Skip to content

Fix GOST 2012 SPKI algorithm OID for legacy CryptoPro parameter sets - #709

Open
danielbarsh wants to merge 1 commit into
bcgit:masterfrom
danielbarsh:fix/gost-2012-cryptopro-spki
Open

Fix GOST 2012 SPKI algorithm OID for legacy CryptoPro parameter sets#709
danielbarsh wants to merge 1 commit into
bcgit:masterfrom
danielbarsh:fix/gost-2012-cryptopro-spki

Conversation

@danielbarsh

Copy link
Copy Markdown

Problem

SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo chose between the GOST R 34.10-2001 algorithm
OID (id-GostR3410-2001) and the GOST R 34.10-2012 OIDs (id-tc26-gost3410-12-256/512) based solely
on whether the key's PublicKeyParamSet (curve) was one of the legacy CryptoPro sets
(A/B/C/XchA/XchB), ignoring DigestParamSet entirely:

// before
private static readonly HashSet<DerObjectIdentifier> cryptoProOids = new HashSet<DerObjectIdentifier>
{
    CryptoProObjectIdentifiers.GostR3410x2001CryptoProA,
    CryptoProObjectIdentifiers.GostR3410x2001CryptoProB,
    CryptoProObjectIdentifiers.GostR3410x2001CryptoProC,
    CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchA,
    CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchB,
};
...
if (cryptoProOids.Contains(gostParams.PublicKeyParamSet))
{
    algOid = CryptoProObjectIdentifiers.GostR3410x2001;
}
else
{
    algOid = fieldSize > 32
        ? RosstandartObjectIdentifiers.id_tc26_gost_3410_12_512
        : RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256;
}

Per RFC 9215's backward-compatibility rules, a legacy CryptoPro curve can legitimately be paired with a
GOST R 34.11-2012 digest — that's a valid GOST 2012 key and must be encoded with a 2012 algorithm OID,
not 2001. The curve alone doesn't determine the algorithm version.

Spec reference

RFC 9215 ("Using GOST R 34.10-2012 and GOST R 34.11-2012 Algorithms with the Internet X.509 Public Key
Infrastructure"), Section 4.2 ("Public Key Parameters"):

When either of these identifiers [id-tc26-gost3410-12-256 / id-tc26-gost3410-12-512] appears as the
algorithm field in the SubjectPublicKeyInfo.algorithm.algorithm field, the parameters field MUST have
the following structure:

GostR3410-2012-PublicKeyParameters ::= SEQUENCE {
    publicKeyParamSet   OBJECT IDENTIFIER,
    digestParamSet      OBJECT IDENTIFIER OPTIONAL
}

and, further in the same section:

digestParamSet MUST be present and must be equal to id-tc26-digest-gost3411-12-256 if one of the
following values is used as publicKeyParamSet: id-GostR3410-2001-TestParamSet,
id-GostR3410-2001-CryptoPro-A-ParamSet, id-GostR3410-2001-CryptoPro-B-ParamSet,
id-GostR3410-2001-CryptoPro-C-ParamSet, id-GostR3410-2001-CryptoPro-XchA-ParamSet,
id-GostR3410-2001-CryptoPro-XchB-ParamSet

So the outer algorithm OID identifies the signature scheme version (2001 vs 2012); publicKeyParamSet
only selects the curve, independently of that — and the RFC's own compatibility rule requires exactly
the CryptoPro-curve + 2012-digest combination this fix handles.

Fix

// after
if (CryptoProObjectIdentifiers.GostR3411x94CryptoProParamSet.Equals(gostParams.DigestParamSet))
{
    algOid = CryptoProObjectIdentifiers.GostR3410x2001;
}
else
{
    algOid = fieldSize > 32
        ? RosstandartObjectIdentifiers.id_tc26_gost_3410_12_512
        : RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256;
}

SubjectPublicKeyInfoFactory now branches on DigestParamSet (legacy GOST R 34.11-94 CryptoPro param
set vs a 2012 digest) instead of on the curve. The 256/512 sub-selection within the 2012 branch is
unchanged (still based on field size, which is curve-derived and unaffected by this change). The
now-unused cryptoProOids set (and the System.Collections.Generic import it needed) was removed.

Note: bc-java's SubjectPublicKeyInfoFactory has the identical curve-only check today, so this isn't a
C#-specific mis-port — happy to raise the equivalent issue there if useful.

Testing

Added Gost2012CryptoProSpkiTest:

[TestFixture]
public class Gost2012CryptoProSpkiTest
    : SimpleTest
{
    public override string Name => "Gost2012CryptoProSpki";

    public override void PerformTest()
    {
        // 1. Select a legacy CryptoPro curve OID (GOST R 34.10-2001 parameter set)
        var curveOid = CryptoProObjectIdentifiers.GostR3410x2001CryptoProA;
        var domain = ECGost3410NamedCurves.GetByOid(curveOid);
        var namedDomain = new ECNamedDomainParameters(curveOid, domain);

        // 2. Configure key parameters with a GOST 2012 (256-bit) digest OID
        var gostParameters = new ECGost3410Parameters(
            namedDomain,
            curveOid,
            RosstandartObjectIdentifiers.id_tc26_gost_3411_12_256,
            null);

        // 3. Generate a key pair based on GOST 2012 parameters
        var keyGen = new ECKeyPairGenerator();
        keyGen.Init(new ECKeyGenerationParameters(gostParameters, new SecureRandom()));
        var keyPair = keyGen.GenerateKeyPair();

        // 4. Wrap the public key into SubjectPublicKeyInfo structure
        var spki = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public);

        string actualAlgOid = spki.Algorithm.Algorithm.Id;
        string expectedAlgOid = RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256.Id;

        // 5. Assert that the algorithm OID corresponds to GOST 2012 rather than GOST 2001
        if (!expectedAlgOid.Equals(actualAlgOid))
        {
            Fail($"Expected GOST 2012 OID ({expectedAlgOid}), but got ({actualAlgOid})");
        }
    }

    [Test]
    public void TestFunction()
    {
        string resultText = Perform().ToString();

        Assert.AreEqual(Name + ": Okay", resultText);
    }
}

This generates a key on a legacy CryptoPro curve (GostR3410x2001CryptoProA) with a GOST 2012 digest
OID and asserts the encoded SubjectPublicKeyInfo carries the 2012 algorithm OID rather than the 2001
one. Also re-ran ECGOST3410_2012Test and Gost3410Test to confirm no regression on the
native-2012-curve and pure-2001-key paths — all pass.

… update GOST parameter check; add unit test for GOST R 34.10-2012 encoding
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant