@@ -395,6 +395,154 @@ public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certific
395395#endif
396396 }
397397
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+
398546#if ! NET10_0_OR_GREATER
399547 private static ArraySegment < byte > GetCertificateSubjectPublicKeyInfo ( X509Certificate2 certificate )
400548 {
0 commit comments