diff --git a/crypto/BouncyCastle.Android.csproj b/crypto/BouncyCastle.Android.csproj
index a356bce2c..41ad0c236 100644
--- a/crypto/BouncyCastle.Android.csproj
+++ b/crypto/BouncyCastle.Android.csproj
@@ -142,6 +142,7 @@
<Compile Include="src\asn1\anssi\ANSSINamedCurves.cs" />
<Compile Include="src\asn1\anssi\ANSSIObjectIdentifiers.cs" />
<Compile Include="src\asn1\bc\BCObjectIdentifiers.cs" />
+ <Compile Include="src\asn1\bc\LinkedCertificate.cs" />
<Compile Include="src\asn1\bsi\BsiObjectIdentifiers.cs" />
<Compile Include="src\asn1\cmp\CAKeyUpdAnnContent.cs" />
<Compile Include="src\asn1\cmp\CertConfirmContent.cs" />
diff --git a/crypto/BouncyCastle.csproj b/crypto/BouncyCastle.csproj
index c79463f7c..8c7f342d2 100644
--- a/crypto/BouncyCastle.csproj
+++ b/crypto/BouncyCastle.csproj
@@ -136,6 +136,7 @@
<Compile Include="src\asn1\anssi\ANSSINamedCurves.cs" />
<Compile Include="src\asn1\anssi\ANSSIObjectIdentifiers.cs" />
<Compile Include="src\asn1\bc\BCObjectIdentifiers.cs" />
+ <Compile Include="src\asn1\bc\LinkedCertificate.cs" />
<Compile Include="src\asn1\bsi\BsiObjectIdentifiers.cs" />
<Compile Include="src\asn1\cmp\CAKeyUpdAnnContent.cs" />
<Compile Include="src\asn1\cmp\CertConfirmContent.cs" />
diff --git a/crypto/BouncyCastle.iOS.csproj b/crypto/BouncyCastle.iOS.csproj
index 630df8a1f..f3ffb3b66 100644
--- a/crypto/BouncyCastle.iOS.csproj
+++ b/crypto/BouncyCastle.iOS.csproj
@@ -137,6 +137,7 @@
<Compile Include="src\asn1\anssi\ANSSINamedCurves.cs" />
<Compile Include="src\asn1\anssi\ANSSIObjectIdentifiers.cs" />
<Compile Include="src\asn1\bc\BCObjectIdentifiers.cs" />
+ <Compile Include="src\asn1\bc\LinkedCertificate.cs" />
<Compile Include="src\asn1\bsi\BsiObjectIdentifiers.cs" />
<Compile Include="src\asn1\cmp\CAKeyUpdAnnContent.cs" />
<Compile Include="src\asn1\cmp\CertConfirmContent.cs" />
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index 38f4544d8..55fcf1704 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -569,6 +569,11 @@
BuildAction = "Compile"
/>
<File
+ RelPath = "src\asn1\bc\LinkedCertificate.cs"
+ SubType = "Code"
+ BuildAction = "Compile"
+ />
+ <File
RelPath = "src\asn1\bsi\BsiObjectIdentifiers.cs"
SubType = "Code"
BuildAction = "Compile"
@@ -11668,6 +11673,11 @@
BuildAction = "Compile"
/>
<File
+ RelPath = "test\src\asn1\test\LinkedCertificateTest.cs"
+ SubType = "Code"
+ BuildAction = "Compile"
+ />
+ <File
RelPath = "test\src\asn1\test\MiscTest.cs"
SubType = "Code"
BuildAction = "Compile"
diff --git a/crypto/src/asn1/Asn1EncodableVector.cs b/crypto/src/asn1/Asn1EncodableVector.cs
index 49532fe57..8a97e8b4f 100644
--- a/crypto/src/asn1/Asn1EncodableVector.cs
+++ b/crypto/src/asn1/Asn1EncodableVector.cs
@@ -61,6 +61,14 @@ namespace Org.BouncyCastle.Asn1
}
}
+ public void AddOptionalTagged(bool isExplicit, int tagNo, Asn1Encodable obj)
+ {
+ if (null != obj)
+ {
+ v.Add(new DerTaggedObject(isExplicit, tagNo, obj));
+ }
+ }
+
public Asn1Encodable this[
int index]
{
diff --git a/crypto/src/asn1/bc/BCObjectIdentifiers.cs b/crypto/src/asn1/bc/BCObjectIdentifiers.cs
index 1e2448853..0ffd65dfc 100644
--- a/crypto/src/asn1/bc/BCObjectIdentifiers.cs
+++ b/crypto/src/asn1/bc/BCObjectIdentifiers.cs
@@ -101,5 +101,14 @@ namespace Org.BouncyCastle.Asn1.BC
* NewHope
*/
public static readonly DerObjectIdentifier newHope = bc_exch.Branch("1");
+
+ /**
+ * X.509 extension(4) values
+ * <p>
+ * 1.3.6.1.4.1.22554.4
+ */
+ public static readonly DerObjectIdentifier bc_ext = bc.Branch("4");
+
+ public static readonly DerObjectIdentifier linkedCertificate = bc_ext.Branch("1");
}
-}
\ No newline at end of file
+}
diff --git a/crypto/src/asn1/bc/LinkedCertificate.cs b/crypto/src/asn1/bc/LinkedCertificate.cs
new file mode 100644
index 000000000..c8d05d8f5
--- /dev/null
+++ b/crypto/src/asn1/bc/LinkedCertificate.cs
@@ -0,0 +1,100 @@
+using System;
+
+using Org.BouncyCastle.Asn1.X509;
+
+namespace Org.BouncyCastle.Asn1.BC
+{
+ /**
+ * Extension to tie an alternate certificate to the containing certificate.
+ * <pre>
+ * LinkedCertificate := SEQUENCE {
+ * digest DigestInfo, -- digest of PQC certificate
+ * certLocation GeneralName, -- location of PQC certificate
+ * certIssuer [0] Name OPTIONAL, -- issuer of PQC cert (if different from current certificate)
+ * cACerts [1] GeneralNames OPTIONAL, -- CA certificates for PQC cert (one of more locations)
+ * }
+ * </pre>
+ */
+ public class LinkedCertificate
+ : Asn1Encodable
+ {
+ private readonly DigestInfo mDigest;
+ private readonly GeneralName mCertLocation;
+
+ private X509Name mCertIssuer;
+ private GeneralNames mCACerts;
+
+ public LinkedCertificate(DigestInfo digest, GeneralName certLocation)
+ : this(digest, certLocation, null, null)
+ {
+ }
+
+ public LinkedCertificate(DigestInfo digest, GeneralName certLocation, X509Name certIssuer, GeneralNames caCerts)
+ {
+ this.mDigest = digest;
+ this.mCertLocation = certLocation;
+ this.mCertIssuer = certIssuer;
+ this.mCACerts = caCerts;
+ }
+
+ private LinkedCertificate(Asn1Sequence seq)
+ {
+ this.mDigest = DigestInfo.GetInstance(seq[0]);
+ this.mCertLocation = GeneralName.GetInstance(seq[1]);
+
+ for (int i = 2; i < seq.Count; ++i)
+ {
+ Asn1TaggedObject tagged = Asn1TaggedObject.GetInstance(seq[i]);
+
+ switch (tagged.TagNo)
+ {
+ case 0:
+ this.mCertIssuer = X509Name.GetInstance(tagged, false);
+ break;
+ case 1:
+ this.mCACerts = GeneralNames.GetInstance(tagged, false);
+ break;
+ default:
+ throw new ArgumentException("unknown tag in tagged field");
+ }
+ }
+ }
+
+ public static LinkedCertificate GetInstance(object obj)
+ {
+ if (obj is LinkedCertificate)
+ return (LinkedCertificate)obj;
+ if (obj != null)
+ return new LinkedCertificate(Asn1Sequence.GetInstance(obj));
+ return null;
+ }
+
+ public virtual DigestInfo Digest
+ {
+ get { return mDigest; }
+ }
+
+ public virtual GeneralName CertLocation
+ {
+ get { return mCertLocation; }
+ }
+
+ public virtual X509Name CertIssuer
+ {
+ get { return mCertIssuer; }
+ }
+
+ public virtual GeneralNames CACerts
+ {
+ get { return mCACerts; }
+ }
+
+ public override Asn1Object ToAsn1Object()
+ {
+ Asn1EncodableVector v = new Asn1EncodableVector(mDigest, mCertLocation);
+ v.AddOptionalTagged(false, 0, mCertIssuer);
+ v.AddOptionalTagged(false, 1, mCACerts);
+ return new DerSequence(v);
+ }
+ }
+}
diff --git a/crypto/test/UnitTests.csproj b/crypto/test/UnitTests.csproj
index 9420cc832..1378034a6 100644
--- a/crypto/test/UnitTests.csproj
+++ b/crypto/test/UnitTests.csproj
@@ -85,6 +85,7 @@
<Compile Include="src\asn1\test\IssuingDistributionPointTest.cs" />
<Compile Include="src\asn1\test\KeyUsageTest.cs" />
<Compile Include="src\asn1\test\LDSSecurityObjectUnitTest.cs" />
+ <Compile Include="src\asn1\test\LinkedCertificateTest.cs" />
<Compile Include="src\asn1\test\MiscTest.cs" />
<Compile Include="src\asn1\test\MonetaryLimitUnitTest.cs" />
<Compile Include="src\asn1\test\MonetaryValueUnitTest.cs" />
diff --git a/crypto/test/src/asn1/test/LinkedCertificateTest.cs b/crypto/test/src/asn1/test/LinkedCertificateTest.cs
new file mode 100644
index 000000000..416c048c0
--- /dev/null
+++ b/crypto/test/src/asn1/test/LinkedCertificateTest.cs
@@ -0,0 +1,97 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Asn1.BC;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.X509;
+
+namespace Org.BouncyCastle.Asn1.Tests
+{
+ [TestFixture]
+ public class LinkedCertificateTest
+ : Asn1UnitTest
+ {
+ public override string Name
+ {
+ get { return "LinkedCertificate"; }
+ }
+
+ public override void PerformTest()
+ {
+ DigestInfo digInfo = new DigestInfo(new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256), new byte[32]);
+ GeneralName certLocation = new GeneralName(GeneralName.UniformResourceIdentifier, "https://www.bouncycastle.org/certs");
+ X509Name certIssuer = null;
+ GeneralNames cACerts = null;
+
+ LinkedCertificate linked = new LinkedCertificate(digInfo, certLocation);
+
+ CheckConstruction(linked, digInfo, certLocation, certIssuer, cACerts);
+
+ certIssuer = new X509Name("CN=Test");
+ cACerts = new GeneralNames(new GeneralName(new X509Name("CN=CA Test")));
+
+ linked = new LinkedCertificate(digInfo, certLocation, certIssuer, cACerts);
+
+ CheckConstruction(linked, digInfo, certLocation, certIssuer, cACerts);
+
+ linked = LinkedCertificate.GetInstance(null);
+
+ if (linked != null)
+ {
+ Fail("null getInstance() failed.");
+ }
+
+ try
+ {
+ LinkedCertificate.GetInstance(new object());
+
+ Fail("getInstance() failed to detect bad object.");
+ }
+ catch (ArgumentException e)
+ {
+ // expected
+ }
+ }
+
+ private void CheckConstruction(LinkedCertificate linked, DigestInfo digestInfo, GeneralName certLocation,
+ X509Name certIssuer, GeneralNames caCerts)
+ {
+ CheckValues(linked, digestInfo, certLocation, certIssuer, caCerts);
+
+ linked = LinkedCertificate.GetInstance(linked);
+
+ CheckValues(linked, digestInfo, certLocation, certIssuer, caCerts);
+
+ Asn1InputStream aIn = new Asn1InputStream(linked.ToAsn1Object().GetEncoded());
+
+ Asn1Sequence seq = (Asn1Sequence)aIn.ReadObject();
+
+ linked = LinkedCertificate.GetInstance(seq);
+
+ CheckValues(linked, digestInfo, certLocation, certIssuer, caCerts);
+ }
+
+ private void CheckValues(LinkedCertificate linked, DigestInfo digestInfo, GeneralName certLocation,
+ X509Name certIssuer, GeneralNames caCerts)
+ {
+ checkMandatoryField("digest", digestInfo, linked.Digest);
+ checkMandatoryField("certLocatin", certLocation, linked.CertLocation);
+ checkOptionalField("certIssuer", certIssuer, linked.CertIssuer);
+ checkOptionalField("caCerts", caCerts, linked.CACerts);
+ }
+
+ public static void Main(string[] args)
+ {
+ RunTest(new LinkedCertificateTest());
+ }
+
+ [Test]
+ public void TestFunction()
+ {
+ string resultText = Perform().ToString();
+
+ Assert.AreEqual(Name + ": Okay", resultText);
+ }
+ }
+}
diff --git a/crypto/test/src/asn1/test/RegressionTest.cs b/crypto/test/src/asn1/test/RegressionTest.cs
index 4534f2c75..67860ccd7 100644
--- a/crypto/test/src/asn1/test/RegressionTest.cs
+++ b/crypto/test/src/asn1/test/RegressionTest.cs
@@ -37,6 +37,7 @@ namespace Org.BouncyCastle.Asn1.Tests
new IssuingDistributionPointUnitTest(),
new KeyUsageTest(),
new LDSSecurityObjectUnitTest(),
+ new LinkedCertificateTest(),
new MiscTest(),
new MonetaryLimitUnitTest(),
new MonetaryValueUnitTest(),
|