File: System\Security\Cryptography\X509Certificates\PublicKey.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Formats.Asn1;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography.Asn1;
 
namespace System.Security.Cryptography.X509Certificates
{
    public sealed class PublicKey
    {
        private readonly Oid _oid;
        private AsymmetricAlgorithm? _key;
 
        public PublicKey(Oid oid, AsnEncodedData parameters, AsnEncodedData keyValue)
            : this(oid, parameters, keyValue, skipCopy: false)
        {
        }
 
        internal PublicKey(Oid oid, AsnEncodedData parameters, AsnEncodedData keyValue, bool skipCopy)
        {
            _oid = oid;
 
            if (skipCopy)
            {
                EncodedParameters = parameters;
                EncodedKeyValue = keyValue;
            }
            else
            {
                EncodedParameters = new AsnEncodedData(parameters);
                EncodedKeyValue = new AsnEncodedData(keyValue);
            }
        }
 
        /// <summary>
        /// Initializes a new instance of the <see cref="PublicKey" /> class
        /// using SubjectPublicKeyInfo from an <see cref="AsymmetricAlgorithm" />.
        /// </summary>
        /// <param name="key">
        /// An asymmetric algorithm to obtain the SubjectPublicKeyInfo from.
        /// </param>
        /// <exception cref="CryptographicException">
        /// The SubjectPublicKeyInfo could not be decoded. The
        /// <see cref="AsymmetricAlgorithm.ExportSubjectPublicKeyInfo" /> must return a
        /// valid ASN.1-DER encoded X.509 SubjectPublicKeyInfo.
        /// </exception>
        /// <exception cref="NotImplementedException">
        /// <see cref="AsymmetricAlgorithm.ExportSubjectPublicKeyInfo" /> has not been overridden
        /// in a derived class.
        /// </exception>
        public PublicKey(AsymmetricAlgorithm key)
        {
            byte[] subjectPublicKey = key.ExportSubjectPublicKeyInfo();
            DecodeSubjectPublicKeyInfo(
                subjectPublicKey,
                out Oid localOid,
                out AsnEncodedData localParameters,
                out AsnEncodedData localKeyValue);
 
            _oid = localOid;
            EncodedParameters = localParameters;
            EncodedKeyValue = localKeyValue;
 
            // Do not assign _key = key. Otherwise, the public Key property
            // will start returning non Rsa / Dsa types.
        }
 
        public AsnEncodedData EncodedKeyValue { get; }
 
        public AsnEncodedData EncodedParameters { get; }
 
        [Obsolete(Obsoletions.PublicKeyPropertyMessage, DiagnosticId = Obsoletions.PublicKeyPropertyDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
        public AsymmetricAlgorithm Key
        {
            get
            {
                if (_key == null)
                {
                    switch (_oid.Value)
                    {
                        case Oids.Rsa:
                        case Oids.Dsa:
                            _key = X509Pal.Instance.DecodePublicKey(_oid, EncodedKeyValue.RawData, EncodedParameters.RawData, null);
                            break;
 
                        default:
                            // This includes ECDSA, because an Oids.EcPublicKey key can be
                            // many different algorithm kinds, not necessarily with mutual exclusion.
                            //
                            // Plus, .NET Framework only supports RSA and DSA in this property.
                            throw new NotSupportedException(SR.NotSupported_KeyAlgorithm);
                    }
                }
 
                return _key;
            }
        }
 
        public Oid Oid => _oid;
 
        /// <summary>
        /// Attempts to export the current key in the X.509 SubjectPublicKeyInfo format into a provided buffer.
        /// </summary>
        /// <param name="destination">
        /// The byte span to receive the X.509 SubjectPublicKeyInfo data.
        /// </param>
        /// <param name="bytesWritten">
        /// When this method returns, contains a value that indicates the number of bytes written to
        /// <paramref name="destination" />. This parameter is treated as uninitialized.
        /// </param>
        /// <returns>
        /// <see langword="true"/> if <paramref name="destination"/> is big enough to receive the output;
        /// otherwise, <see langword="false"/>.
        /// </returns>
        public bool TryExportSubjectPublicKeyInfo(Span<byte> destination, out int bytesWritten) =>
            EncodeSubjectPublicKeyInfo().TryEncode(destination, out bytesWritten);
 
        /// <summary>
        /// Exports the current key in the X.509 SubjectPublicKeyInfo format.
        /// </summary>
        /// <returns>
        /// A byte array containing the X.509 SubjectPublicKeyInfo representation of this key.
        /// </returns>
        public byte[] ExportSubjectPublicKeyInfo() =>
            EncodeSubjectPublicKeyInfo().Encode();
 
        /// <summary>
        /// Creates a new instance of <see cref="PublicKey" /> from a X.509 SubjectPublicKeyInfo.
        /// </summary>
        /// <param name="source">
        /// The bytes of an X.509 SubjectPublicKeyInfo structure in the ASN.1-DER encoding.
        /// </param>
        /// <param name="bytesRead">
        /// When this method returns, contains a value that indicates the number of bytes read from
        /// <paramref name="source" />. This parameter is treated as uninitialized.
        /// </param>
        /// <returns>A public key representing the SubjectPublicKeyInfo.</returns>
        /// <exception cref="CryptographicException">
        /// The SubjectPublicKeyInfo could not be decoded.
        /// </exception>
        public static PublicKey CreateFromSubjectPublicKeyInfo(ReadOnlySpan<byte> source, out int bytesRead)
        {
            int read = DecodeSubjectPublicKeyInfo(
                source,
                out Oid localOid,
                out AsnEncodedData localParameters,
                out AsnEncodedData localKeyValue);
 
            bytesRead = read;
            return new PublicKey(localOid, localParameters, localKeyValue, skipCopy: true);
        }
 
        /// <summary>
        /// Gets the <see cref="RSA" /> public key, or <see langword="null" /> if the key is not an RSA key.
        /// </summary>
        /// <returns>
        /// The public key, or <see langword="null" /> if the key is not an RSA key.
        /// </returns>
        /// <exception cref="CryptographicException">
        /// The key contents are corrupt or could not be read successfully.
        /// </exception>
        [UnsupportedOSPlatform("browser")]
        public RSA? GetRSAPublicKey()
        {
            if (_oid.Value != Oids.Rsa)
                return null;
 
            RSA rsa = RSA.Create();
 
            try
            {
                rsa.ImportSubjectPublicKeyInfo(ExportSubjectPublicKeyInfo(), out _);
                return rsa;
            }
            catch
            {
                rsa.Dispose();
                throw;
            }
        }
 
        /// <summary>
        /// Gets the <see cref="DSA" /> public key, or <see langword="null" /> if the key is not an DSA key.
        /// </summary>
        /// <returns>
        /// The public key, or <see langword="null" /> if the key is not an DSA key.
        /// </returns>
        /// <exception cref="CryptographicException">
        /// The key contents are corrupt or could not be read successfully.
        /// </exception>
        [UnsupportedOSPlatform("browser")]
        [UnsupportedOSPlatform("ios")]
        [UnsupportedOSPlatform("tvos")]
        public DSA? GetDSAPublicKey()
        {
            if (_oid.Value != Oids.Dsa)
                return null;
 
            DSA dsa = DSA.Create();
 
            try
            {
                dsa.ImportSubjectPublicKeyInfo(ExportSubjectPublicKeyInfo(), out _);
                return dsa;
            }
            catch
            {
                dsa.Dispose();
                throw;
            }
        }
 
        /// <summary>
        /// Gets the <see cref="ECDsa" /> public key, or <see langword="null" /> if the key is not an ECDsa key.
        /// </summary>
        /// <returns>
        /// The public key, or <see langword="null" /> if the key is not an ECDsa key.
        /// </returns>
        /// <exception cref="CryptographicException">
        /// The key contents are corrupt or could not be read successfully.
        /// </exception>
        [UnsupportedOSPlatform("browser")]
        public ECDsa? GetECDsaPublicKey()
        {
            if (_oid.Value != Oids.EcPublicKey)
                return null;
 
            ECDsa ecdsa = ECDsa.Create();
 
            try
            {
                ecdsa.ImportSubjectPublicKeyInfo(ExportSubjectPublicKeyInfo(), out _);
                return ecdsa;
            }
            catch
            {
                ecdsa.Dispose();
                throw;
            }
        }
 
        /// <summary>
        /// Gets the <see cref="ECDiffieHellman" /> public key, or <see langword="null" />
        /// if the key is not an ECDiffieHellman key.
        /// </summary>
        /// <returns>
        /// The public key, or <see langword="null" /> if the key is not an ECDiffieHellman key.
        /// </returns>
        /// <exception cref="CryptographicException">
        /// The key contents are corrupt or could not be read successfully.
        /// </exception>
        [UnsupportedOSPlatform("browser")]
        public ECDiffieHellman? GetECDiffieHellmanPublicKey()
        {
            if (_oid.Value != Oids.EcPublicKey)
                return null;
 
            ECDiffieHellman ecdh = ECDiffieHellman.Create();
 
            try
            {
                ecdh.ImportSubjectPublicKeyInfo(ExportSubjectPublicKeyInfo(), out _);
                return ecdh;
            }
            catch
            {
                ecdh.Dispose();
                throw;
            }
        }
 
        internal AsnWriter EncodeSubjectPublicKeyInfo()
        {
            SubjectPublicKeyInfoAsn spki = new SubjectPublicKeyInfoAsn
            {
                Algorithm = new AlgorithmIdentifierAsn
                {
                    Algorithm = _oid.Value ?? string.Empty,
                    Parameters = EncodedParameters.RawData,
                },
                SubjectPublicKey = EncodedKeyValue.RawData,
            };
 
            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
            spki.Encode(writer);
            return writer;
        }
 
        private static unsafe int DecodeSubjectPublicKeyInfo(
            ReadOnlySpan<byte> source,
            out Oid oid,
            out AsnEncodedData parameters,
            out AsnEncodedData keyValue)
        {
            fixed (byte* ptr = &MemoryMarshal.GetReference(source))
            using (MemoryManager<byte> manager = new PointerMemoryManager<byte>(ptr, source.Length))
            {
                AsnValueReader reader = new AsnValueReader(source, AsnEncodingRules.DER);
 
                int read;
                SubjectPublicKeyInfoAsn spki;
 
                try
                {
                    read = reader.PeekEncodedValue().Length;
                    SubjectPublicKeyInfoAsn.Decode(ref reader, manager.Memory, out spki);
                }
                catch (AsnContentException e)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
                }
 
                DecodeSubjectPublicKeyInfo(ref spki, out oid, out parameters, out keyValue);
                return read;
            }
        }
 
        internal static PublicKey DecodeSubjectPublicKeyInfo(ref SubjectPublicKeyInfoAsn spki)
        {
            DecodeSubjectPublicKeyInfo(
                ref spki,
                out Oid oid,
                out AsnEncodedData parameters,
                out AsnEncodedData keyValue);
 
            return new PublicKey(oid, parameters, keyValue, skipCopy: true);
        }
 
        private static void DecodeSubjectPublicKeyInfo(
            ref SubjectPublicKeyInfoAsn spki,
            out Oid oid,
            out AsnEncodedData parameters,
            out AsnEncodedData keyValue)
        {
            oid = new Oid(spki.Algorithm.Algorithm, null);
            parameters = new AsnEncodedData(spki.Algorithm.Parameters.GetValueOrDefault().Span);
            keyValue = new AsnEncodedData(spki.SubjectPublicKey.Span);
        }
 
    }
}