Skip to content

[BUG] SubjectPublicKeyInfoFactory encodes GOST R 34.10-2012 key with CryptoPro parameter set as GOST R 34.10-2001 #707

Description

@gisinka

SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo encodes a GOST R 34.10-2012-256 key as GOST R 34.10-2001 when the key uses a legacy CryptoPro parameter set OID.

The following combination is valid:

  • Key algorithm: 1.2.643.7.1.1.1.1 — GOST R 34.10-2012-256
  • Public key parameter set: 1.2.643.2.2.36.0 — CryptoPro-XchA
  • Digest: 1.2.643.7.1.1.2.2 — GOST R 34.11-2012-256

However, Bouncy Castle 2.7.0 serializes the resulting SubjectPublicKeyInfo with:

1.2.643.2.2.19 — GOST R 34.10-2001

instead of:

1.2.643.7.1.1.1.1 — GOST R 34.10-2012-256.

RFC 9215 section 4.2 allows GOST R 34.10-2001 parameter-set identifiers to be used as publicKeyParamSet values for GOST R 34.10-2012 keys.

The issue appears to be caused by SubjectPublicKeyInfoFactory selecting the key algorithm based only on whether PublicKeyParamSet is present in cryptoProOids.

To Reproduce

Create GOST parameters using CryptoPro-XchA as the public key parameter set and GOST R 34.11-2012-256 as the digest:

var curveOid =
    CryptoProObjectIdentifiers.GostR3410x2001CryptoProXchA;

var domain =
    ECGost3410NamedCurves.GetByOid(curveOid);

var namedDomain = new ECNamedDomainParameters(
    curveOid,
    domain);

var gostParameters = new ECGost3410Parameters(
    namedDomain,
    curveOid,
    RosstandartObjectIdentifiers.id_tc26_gost_3411_12_256,
    null);

var generationParameters = new ECKeyGenerationParameters(
    gostParameters,
    new SecureRandom());

var generator = new ECKeyPairGenerator();
generator.Init(generationParameters);

var keyPair = generator.GenerateKeyPair();

var spki =
    SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(
        keyPair.Public);

Console.WriteLine(spki.Algorithm.Algorithm.Id);

Actual output:

1.2.643.2.2.19

The relevant code in SubjectPublicKeyInfoFactory is:

return new SubjectPublicKeyInfo(
    new AlgorithmIdentifier(
        !cryptoProOids.Contains(parameters7.PublicKeyParamSet)
            ? (elementEncodingLength > 32
                ? RosstandartObjectIdentifiers.id_tc26_gost_3410_12_512
                : RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256)
            : CryptoProObjectIdentifiers.GostR3410x2001,
        new Gost3410PublicKeyAlgParameters(
            parameters7.PublicKeyParamSet,
            parameters7.DigestParamSet,
            parameters7.EncryptionParamSet)),
    CreateECGost3410PublicKey(elementEncodingLength, q));

Since 1.2.643.2.2.36.0 is included in cryptoProOids, the key algorithm is always selected as GOST R 34.10-2001.

Expected Behavior

SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo should be able to produce:

Algorithm:
    1.2.643.7.1.1.1.1

Parameters:
    publicKeyParamSet: 1.2.643.2.2.36.0
    digestParamSet:    1.2.643.7.1.1.2.2

That is:

GOST R 34.10-2012-256
+ CryptoPro-XchA parameter set
+ GOST R 34.11-2012-256

There should either be a way to explicitly specify the GOST key algorithm independently of the parameter-set OID, or SubjectPublicKeyInfoFactory should not assume that every CryptoPro parameter-set OID implies GOST R 34.10-2001.

As a workaround, constructing the AlgorithmIdentifier explicitly produces the expected SubjectPublicKeyInfo:

var bcSpki =
    SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(
        keyPair.Public);

var algorithmIdentifier = new AlgorithmIdentifier(
    RosstandartObjectIdentifiers.id_tc26_gost_3410_12_256,
    new Gost3410PublicKeyAlgParameters(
        curveOid,
        RosstandartObjectIdentifiers.id_tc26_gost_3411_12_256));

var correctSpki = new SubjectPublicKeyInfo(
    algorithmIdentifier,
    bcSpki.ParsePublicKey());

Screenshots and Logs

BouncyCastle version: 2.7.0

Input ECGost3410Parameters:

PublicKeyParamSet : 1.2.643.2.2.36.0
DigestParamSet    : 1.2.643.7.1.1.2.2
Field size        : 256 bits

SubjectPublicKeyInfoFactory output:

Algorithm         : 1.2.643.2.2.19
PublicKeyParamSet : 1.2.643.2.2.36.0
DigestParamSet    : 1.2.643.7.1.1.2.2

Expected SubjectPublicKeyInfo:

Algorithm         : 1.2.643.7.1.1.1.1
PublicKeyParamSet : 1.2.643.2.2.36.0
DigestParamSet    : 1.2.643.7.1.1.2.2

Manually replacing only the AlgorithmIdentifier while keeping the public key encoding generated by Bouncy Castle produces the expected certificate.

Product Deployment

  • Deployment format: NuGet package / .NET application
  • Package: BouncyCastle.Cryptography
  • Version: 2.7.0
  • Runtime: .NET 10

Desktop

  • OS: macOS
  • Browser: N/A
  • Version: N/A

Additional Context

RFC 9215 section 4.2 allows GOST R 34.10-2001 parameter-set identifiers to be used with GOST R 34.10-2012 public keys.

The specific combination required here is:

key algorithm      = 1.2.643.7.1.1.1.1
publicKeyParamSet  = 1.2.643.2.2.36.0
digestParamSet     = 1.2.643.7.1.1.2.2

This combination is also accepted by CryptoPro CSP.

The current implementation seems unable to represent it through ECGost3410Parameters and SubjectPublicKeyInfoFactory, because the presence of a CryptoPro parameter-set OID forces the algorithm OID to GOST R 34.10-2001.

Is there an intended API in Bouncy Castle 2.7.0 for explicitly selecting GOST R 34.10-2012 while retaining a legacy CryptoPro parameter-set OID?

If not, cryptoProOids.Contains(PublicKeyParamSet) does not appear to be sufficient to distinguish GOST R 34.10-2001 keys from GOST R 34.10-2012 keys.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions