@@ -395,6 +395,154 @@ public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certific
395
395
#endif
396
396
}
397
397
398
+ /// <summary>
399
+ /// Gets the <see cref="CompositeMLDsa"/> public key from this certificate.
400
+ /// </summary>
401
+ /// <param name="certificate">
402
+ /// The X.509 certificate that contains the public key.
403
+ /// </param>
404
+ /// <returns>
405
+ /// The public key, or <see langword="null"/> if this certificate does not have a Composite ML-DSA public key.
406
+ /// </returns>
407
+ /// <exception cref="ArgumentNullException">
408
+ /// <paramref name="certificate"/> is <see langword="null"/>.
409
+ /// </exception>
410
+ /// <exception cref="PlatformNotSupportedException">
411
+ /// The certificate has a Composite ML-DSA public key, but the platform does not support Composite ML-DSA.
412
+ /// </exception>
413
+ /// <exception cref="CryptographicException">
414
+ /// The public key was invalid, or otherwise could not be imported.
415
+ /// </exception>
416
+ [ Experimental ( Experimentals . PostQuantumCryptographyDiagId , UrlFormat = Experimentals . SharedUrlFormat ) ]
417
+ public static CompositeMLDsa ? GetCompositeMLDsaPublicKey ( this X509Certificate2 certificate )
418
+ {
419
+ ArgumentNullException . ThrowIfNull ( certificate ) ;
420
+
421
+ #if NET10_0_OR_GREATER
422
+ return certificate . GetCompositeMLDsaPublicKey ( ) ;
423
+ #else
424
+ if ( CompositeMLDsaAlgorithm . GetAlgorithmFromOid ( certificate . GetKeyAlgorithm ( ) ) is null )
425
+ {
426
+ return null ;
427
+ }
428
+
429
+ ArraySegment < byte > encoded = GetCertificateSubjectPublicKeyInfo ( certificate ) ;
430
+
431
+ try
432
+ {
433
+ return CompositeMLDsa . ImportSubjectPublicKeyInfo ( encoded ) ;
434
+ }
435
+ finally
436
+ {
437
+ // SubjectPublicKeyInfo does not need to clear since it's public
438
+ CryptoPool . Return ( encoded , clearSize : 0 ) ;
439
+ }
440
+ #endif
441
+ }
442
+
443
+ /// <summary>
444
+ /// Gets the <see cref="CompositeMLDsa"/> private key from this certificate.
445
+ /// </summary>
446
+ /// <param name="certificate">
447
+ /// The X.509 certificate that contains the private key.
448
+ /// </param>
449
+ /// <returns>
450
+ /// The private key, or <see langword="null"/> if this certificate does not have a Composite ML-DSA private key.
451
+ /// </returns>
452
+ /// <exception cref="ArgumentNullException">
453
+ /// <paramref name="certificate"/> is <see langword="null"/>.
454
+ /// </exception>
455
+ /// <exception cref="PlatformNotSupportedException">
456
+ /// Retrieving a Composite ML-DSA private key from a certificate is not supported on this platform.
457
+ /// </exception>
458
+ /// <exception cref="CryptographicException">
459
+ /// An error occurred accessing the private key.
460
+ /// </exception>
461
+ [ Experimental ( Experimentals . PostQuantumCryptographyDiagId , UrlFormat = Experimentals . SharedUrlFormat ) ]
462
+ public static CompositeMLDsa ? GetCompositeMLDsaPrivateKey ( this X509Certificate2 certificate )
463
+ {
464
+ ArgumentNullException . ThrowIfNull ( certificate ) ;
465
+
466
+ #if NET10_0_OR_GREATER
467
+ return certificate . GetCompositeMLDsaPrivateKey ( ) ;
468
+ #else
469
+ if ( CompositeMLDsaAlgorithm . GetAlgorithmFromOid ( certificate . GetKeyAlgorithm ( ) ) is null )
470
+ {
471
+ return null ;
472
+ }
473
+
474
+ throw new PlatformNotSupportedException ( ) ;
475
+ #endif
476
+ }
477
+
478
+ /// <summary>
479
+ /// Combines a private key with a certificate containing the associated public key into a
480
+ /// new instance that can access the private key.
481
+ /// </summary>
482
+ /// <param name="certificate">
483
+ /// The X.509 certificate that contains the public key.
484
+ /// </param>
485
+ /// <param name="privateKey">
486
+ /// The Composite ML-DSA private key that corresponds to the Composite ML-DSA public key in this certificate.
487
+ /// </param>
488
+ /// <returns>
489
+ /// A new certificate with the <see cref="X509Certificate2.HasPrivateKey" /> property set to <see langword="true"/>.
490
+ /// The current certificate isn't modified.
491
+ /// </returns>
492
+ /// <exception cref="ArgumentNullException">
493
+ /// <paramref name="certificate"/> or <paramref name="privateKey"/> is <see langword="null"/>.
494
+ /// </exception>
495
+ /// <exception cref="ArgumentException">
496
+ /// The specified private key doesn't match the public key for this certificate.
497
+ /// </exception>
498
+ /// <exception cref="InvalidOperationException">
499
+ /// The certificate already has an associated private key.
500
+ /// </exception>
501
+ /// <exception cref="PlatformNotSupportedException">
502
+ /// Combining a certificate and a Composite ML-DSA private key is not supported on this platform.
503
+ /// </exception>
504
+ [ Experimental ( Experimentals . PostQuantumCryptographyDiagId , UrlFormat = Experimentals . SharedUrlFormat ) ]
505
+ public static X509Certificate2 CopyWithPrivateKey ( this X509Certificate2 certificate , CompositeMLDsa privateKey )
506
+ {
507
+ ArgumentNullException . ThrowIfNull ( certificate ) ;
508
+ ArgumentNullException . ThrowIfNull ( privateKey ) ;
509
+
510
+ #if NET10_0_OR_GREATER
511
+ return certificate . CopyWithPrivateKey ( privateKey ) ;
512
+ #elif NETSTANDARD
513
+ throw new PlatformNotSupportedException ( SR . Format ( SR . Cryptography_AlgorithmNotSupported , nameof ( CompositeMLDsa ) ) ) ;
514
+ #else
515
+ if ( ! Helpers . IsOSPlatformWindows )
516
+ throw new PlatformNotSupportedException ( ) ;
517
+
518
+ if ( certificate . HasPrivateKey )
519
+ throw new InvalidOperationException ( SR . Cryptography_Cert_AlreadyHasPrivateKey ) ;
520
+
521
+ using ( CompositeMLDsa ? publicKey = GetCompositeMLDsaPublicKey ( certificate ) )
522
+ {
523
+ if ( publicKey is null )
524
+ {
525
+ throw new ArgumentException ( SR . Cryptography_PrivateKey_WrongAlgorithm ) ;
526
+ }
527
+
528
+ if ( publicKey . Algorithm != privateKey . Algorithm )
529
+ {
530
+ throw new ArgumentException ( SR . Cryptography_PrivateKey_DoesNotMatch , nameof ( privateKey ) ) ;
531
+ }
532
+
533
+ byte [ ] pk1 = publicKey . ExportCompositeMLDsaPublicKey ( ) ;
534
+ byte [ ] pk2 = privateKey . ExportCompositeMLDsaPublicKey ( ) ;
535
+
536
+ if ( ! pk1 . SequenceEqual ( pk2 ) )
537
+ {
538
+ throw new ArgumentException ( SR . Cryptography_PrivateKey_DoesNotMatch , nameof ( privateKey ) ) ;
539
+ }
540
+ }
541
+
542
+ throw new PlatformNotSupportedException ( ) ;
543
+ #endif
544
+ }
545
+
398
546
#if ! NET10_0_OR_GREATER
399
547
private static ArraySegment < byte > GetCertificateSubjectPublicKeyInfo ( X509Certificate2 certificate )
400
548
{
0 commit comments