22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System ;
5+ using System . Buffers ;
6+ using System . Diagnostics ;
57using System . Diagnostics . CodeAnalysis ;
8+ using System . Formats . Asn1 ;
69using System . Runtime . CompilerServices ;
10+ using System . Security . Cryptography . Asn1 ;
11+ using Internal . Cryptography ;
712
813namespace System . Security . Cryptography
914{
@@ -74,7 +79,7 @@ protected MLKem(MLKemAlgorithm algorithm)
7479 /// </exception>
7580 /// <exception cref="PlatformNotSupportedException">
7681 /// The platform does not support ML-KEM. Callers can use the <see cref="IsSupported" /> property
77- /// to determine if the platform supports MK -KEM.
82+ /// to determine if the platform supports ML -KEM.
7883 /// </exception>
7984 public static MLKem GenerateKey ( MLKemAlgorithm algorithm )
8085 {
@@ -462,7 +467,7 @@ public byte[] ExportPrivateSeed()
462467 /// </exception>
463468 /// <exception cref="PlatformNotSupportedException">
464469 /// The platform does not support ML-KEM. Callers can use the <see cref="IsSupported" /> property
465- /// to determine if the platform supports MK -KEM.
470+ /// to determine if the platform supports ML -KEM.
466471 /// </exception>
467472 public static MLKem ImportPrivateSeed ( MLKemAlgorithm algorithm , ReadOnlySpan < byte > source )
468473 {
@@ -495,7 +500,7 @@ public static MLKem ImportPrivateSeed(MLKemAlgorithm algorithm, ReadOnlySpan<byt
495500 /// </exception>
496501 /// <exception cref="PlatformNotSupportedException">
497502 /// The platform does not support ML-KEM. Callers can use the <see cref="IsSupported" /> property
498- /// to determine if the platform supports MK -KEM.
503+ /// to determine if the platform supports ML -KEM.
499504 /// </exception>
500505 public static MLKem ImportPrivateSeed ( MLKemAlgorithm algorithm , byte [ ] source )
501506 {
@@ -521,7 +526,7 @@ public static MLKem ImportPrivateSeed(MLKemAlgorithm algorithm, byte[] source)
521526 /// </exception>
522527 /// <exception cref="PlatformNotSupportedException">
523528 /// The platform does not support ML-KEM. Callers can use the <see cref="IsSupported" /> property
524- /// to determine if the platform supports MK -KEM.
529+ /// to determine if the platform supports ML -KEM.
525530 /// </exception>
526531 public static MLKem ImportDecapsulationKey ( MLKemAlgorithm algorithm , ReadOnlySpan < byte > source )
527532 {
@@ -553,7 +558,7 @@ public static MLKem ImportDecapsulationKey(MLKemAlgorithm algorithm, ReadOnlySpa
553558 /// </exception>
554559 /// <exception cref="PlatformNotSupportedException">
555560 /// The platform does not support ML-KEM. Callers can use the <see cref="IsSupported" /> property
556- /// to determine if the platform supports MK -KEM.
561+ /// to determine if the platform supports ML -KEM.
557562 /// </exception>
558563 public static MLKem ImportDecapsulationKey ( MLKemAlgorithm algorithm , byte [ ] source )
559564 {
@@ -578,7 +583,7 @@ public static MLKem ImportDecapsulationKey(MLKemAlgorithm algorithm, byte[] sour
578583 /// </exception>
579584 /// <exception cref="PlatformNotSupportedException">
580585 /// The platform does not support ML-KEM. Callers can use the <see cref="IsSupported" /> property
581- /// to determine if the platform supports MK -KEM.
586+ /// to determine if the platform supports ML -KEM.
582587 /// </exception>
583588 public static MLKem ImportEncapsulationKey ( MLKemAlgorithm algorithm , ReadOnlySpan < byte > source )
584589 {
@@ -610,7 +615,7 @@ public static MLKem ImportEncapsulationKey(MLKemAlgorithm algorithm, ReadOnlySpa
610615 /// </exception>
611616 /// <exception cref="PlatformNotSupportedException">
612617 /// The platform does not support ML-KEM. Callers can use the <see cref="IsSupported" /> property
613- /// to determine if the platform supports MK -KEM.
618+ /// to determine if the platform supports ML -KEM.
614619 /// </exception>
615620 public static MLKem ImportEncapsulationKey ( MLKemAlgorithm algorithm , byte [ ] source )
616621 {
@@ -727,6 +732,119 @@ public byte[] ExportEncapsulationKey()
727732 /// </param>
728733 protected abstract void ExportEncapsulationKeyCore ( Span < byte > destination ) ;
729734
735+ /// <summary>
736+ /// Attempts to export the public-key portion of the current key in the X.509 SubjectPublicKeyInfo format
737+ /// into the provided buffer.
738+ /// </summary>
739+ /// <param name="destination">
740+ /// The buffer to receive the X.509 SubjectPublicKeyInfo value.
741+ /// </param>
742+ /// <param name="bytesWritten">
743+ /// When this method returns, contains the number of bytes written to the <paramref name="destination"/> buffer.
744+ /// This parameter is treated as uninitialized.
745+ /// </param>
746+ /// <returns>
747+ /// <see langword="true" /> if <paramref name="destination"/> was large enough to hold the result;
748+ /// otherwise, <see langword="false" />.
749+ /// </returns>
750+ /// <exception cref="ObjectDisposedException">
751+ /// This instance has been disposed.
752+ /// </exception>
753+ /// <exception cref="CryptographicException">
754+ /// An error occurred while exporting the key.
755+ /// </exception>
756+ public bool TryExportSubjectPublicKeyInfo ( Span < byte > destination , out int bytesWritten )
757+ {
758+ ThrowIfDisposed ( ) ;
759+ return ExportSubjectPublicKeyInfoCore ( ) . TryEncode ( destination , out bytesWritten ) ;
760+ }
761+
762+ /// <summary>
763+ /// Exports the public-key portion of the current key in the X.509 SubjectPublicKeyInfo format.
764+ /// </summary>
765+ /// <returns>
766+ /// A byte array containing the X.509 SubjectPublicKeyInfo representation of the public-key portion of this key.
767+ /// </returns>
768+ /// <exception cref="ObjectDisposedException">
769+ /// This instance has been disposed.
770+ /// </exception>
771+ /// <exception cref="CryptographicException">
772+ /// An error occurred while exporting the key.
773+ /// </exception>
774+ public byte [ ] ExportSubjectPublicKeyInfo ( )
775+ {
776+ ThrowIfDisposed ( ) ;
777+ return ExportSubjectPublicKeyInfoCore ( ) . Encode ( ) ;
778+ }
779+
780+ /// <summary>
781+ /// Imports an ML-KEM encapsulation key from an X.509 SubjectPublicKeyInfo structure.
782+ /// </summary>
783+ /// <param name="source">
784+ /// The bytes of an X.509 SubjectPublicKeyInfo structure in the ASN.1-DER encoding.
785+ /// </param>
786+ /// <returns>
787+ /// The imported key.
788+ /// </returns>
789+ /// <exception cref="CryptographicException">
790+ /// <para>
791+ /// The contents of <paramref name="source"/> do not represent an ASN.1-DER-encoded X.509 SubjectPublicKeyInfo structure.
792+ /// </para>
793+ /// <para>-or-</para>
794+ /// <para>
795+ /// The SubjectPublicKeyInfo value does not represent an ML-KEM key.
796+ /// </para>
797+ /// <para>-or-</para>
798+ /// <para>
799+ /// The algorithm-specific import failed.
800+ /// </para>
801+ /// </exception>
802+ /// <exception cref="PlatformNotSupportedException">
803+ /// The platform does not support ML-KEM. Callers can use the <see cref="IsSupported" /> property
804+ /// to determine if the platform supports ML-KEM.
805+ /// </exception>
806+ public static MLKem ImportSubjectPublicKeyInfo ( ReadOnlySpan < byte > source )
807+ {
808+ ThrowIfNotSupported ( ) ;
809+
810+ unsafe
811+ {
812+ fixed ( byte * pointer = source )
813+ {
814+ using ( PointerMemoryManager < byte > manager = new ( pointer , source . Length ) )
815+ {
816+ AsnValueReader reader = new ( source , AsnEncodingRules . DER ) ;
817+ SubjectPublicKeyInfoAsn . Decode ( ref reader , manager . Memory , out SubjectPublicKeyInfoAsn spki ) ;
818+ MLKemAlgorithm algorithm = MLKemAlgorithm . FromOid ( spki . Algorithm . Algorithm ) ??
819+ throw new CryptographicException (
820+ SR . Format ( SR . Cryptography_UnknownAlgorithmIdentifier ,
821+ spki . Algorithm . Algorithm ) ) ;
822+
823+ // draft-ietf-lamps-kyber-certificates-07:
824+ // The parameters field of the AlgorithmIdentifier for the ML-KEM public key MUST be absent.
825+ if ( spki . Algorithm . Parameters . HasValue )
826+ {
827+ AsnWriter writer = new AsnWriter ( AsnEncodingRules . DER ) ;
828+ spki . Algorithm . Encode ( writer ) ;
829+ throw Helpers . CreateAlgorithmUnknownException ( writer ) ;
830+ }
831+
832+ return MLKemImplementation . ImportEncapsulationKeyImpl ( algorithm , spki . SubjectPublicKey . Span ) ;
833+ }
834+ }
835+ }
836+ }
837+
838+ /// <inheritdoc cref="ImportSubjectPublicKeyInfo(ReadOnlySpan{byte})" />
839+ /// <exception cref="ArgumentNullException">
840+ /// <paramref name="source" /> is <see langword="null" />
841+ /// </exception>
842+ public static MLKem ImportSubjectPublicKeyInfo ( byte [ ] source )
843+ {
844+ ThrowIfNull ( source ) ;
845+ return ImportSubjectPublicKeyInfo ( new ReadOnlySpan < byte > ( source ) ) ;
846+ }
847+
730848 /// <summary>
731849 /// Releases all resources used by the <see cref="MLKem"/> class.
732850 /// </summary>
@@ -752,6 +870,40 @@ protected virtual void Dispose(bool disposing)
752870 {
753871 }
754872
873+ private AsnWriter ExportSubjectPublicKeyInfoCore ( )
874+ {
875+ int encapsulationKeySize = Algorithm . EncapsulationKeySizeInBytes ;
876+ byte [ ] encapsulationKeyBuffer = CryptoPool . Rent ( encapsulationKeySize ) ;
877+ Memory < byte > encapsulationKey = encapsulationKeyBuffer . AsMemory ( 0 , encapsulationKeySize ) ;
878+
879+ try
880+ {
881+ ExportEncapsulationKeyCore ( encapsulationKey . Span ) ;
882+
883+ SubjectPublicKeyInfoAsn spki = new SubjectPublicKeyInfoAsn
884+ {
885+ Algorithm = new AlgorithmIdentifierAsn
886+ {
887+ Algorithm = Algorithm . Oid ,
888+ Parameters = default ( ReadOnlyMemory < byte > ? ) ,
889+ } ,
890+ SubjectPublicKey = encapsulationKey ,
891+ } ;
892+
893+ // The ASN.1 overhead of a SubjectPublicKeyInfo encoding an encapsulation key is 22 bytes.
894+ // Round it off to 32. This checked operation should never throw because the inputs are not
895+ // user provided.
896+ int capacity = checked ( 32 + encapsulationKeySize ) ;
897+ AsnWriter writer = new AsnWriter ( AsnEncodingRules . DER , capacity ) ;
898+ spki . Encode ( writer ) ;
899+ return writer ;
900+ }
901+ finally
902+ {
903+ CryptoPool . Return ( encapsulationKeyBuffer , clearSize : 0 ) ; // SPKI is public info, skip clear.
904+ }
905+ }
906+
755907 private protected static void ThrowIfNotSupported ( )
756908 {
757909 if ( ! IsSupported )
0 commit comments