< Summary

Class:Azure.Identity.LightweightPkcs8Decoder
Assembly:Azure.Identity
File(s):C:\Git\azure-sdk-for-net\sdk\identity\Azure.Identity\src\LightweightPkcs8Decoder.cs
Covered lines:61
Uncovered lines:10
Coverable lines:71
Total lines:177
Line coverage:85.9% (61 of 71)
Covered branches:20
Total branches:30
Branch coverage:66.6% (20 of 30)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.cctor()-100%100%
ReadLength(...)-90.91%83.33%
ReadUnsignedInteger(...)-70%64.29%
ConsumeFullPayloadTag(...)-83.33%75%
ConsumeMatch(...)-80%50%
DecodeRSAPkcs8(...)-95.45%50%

File(s)

C:\Git\azure-sdk-for-net\sdk\identity\Azure.Identity\src\LightweightPkcs8Decoder.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.IO;
 6using System.Linq;
 7using System.Security.Cryptography;
 8
 9namespace Azure.Identity
 10{
 11    /// <summary>
 12    /// This is a very targeted PKCS#8 decoder for use when reading a PKCS# encoded RSA private key from an
 13    /// DER encoded ASN.1 blob. In an ideal world, we would be able to call AsymmetricAlgorithm.ImportPkcs8PrivateKey
 14    /// off an RSA object to import the private key from a byte array, which we got from the PEM file. There
 15    /// are a few issues with this however:
 16    ///
 17    /// 1. ImportPkcs8PrivateKey does not exist in the Desktop .NET Framework as of today.
 18    /// 2. ImportPkcs8PrivateKey was added to .NET Core in 3.0, and we'd love to be able to support this
 19    ///    on older versions of .NET Core.
 20    ///
 21    /// This code is able to decode RSA keys (without any attributes) from well formed PKCS#8 blobs.
 22    /// </summary>
 23    internal class LightweightPkcs8Decoder
 24    {
 225        private static readonly byte[] s_derIntegerZero = { 0x02, 0x01, 0x00 };
 26
 227        private static readonly byte[] s_rsaAlgorithmId =
 228        {
 229            0x30, 0x0D,
 230            0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
 231            0x05, 0x00,
 232        };
 33
 34        private static int ReadLength(byte[] data, ref int offset)
 35        {
 17636            byte lengthOrLengthLength = data[offset++];
 37
 17638            if (lengthOrLengthLength < 0x80)
 39            {
 1640                return lengthOrLengthLength;
 41            }
 42
 16043            int lengthLength = lengthOrLengthLength & 0x7F;
 16044            int length = 0;
 45
 96046            for (int i = 0; i < lengthLength; i++)
 47            {
 32048                if (length > ushort.MaxValue)
 49                {
 050                    throw new InvalidDataException("Invalid PKCS#8 Data");
 51                }
 52
 32053                length <<= 8;
 32054                length |= data[offset++];
 55            }
 56
 16057            return length;
 58        }
 59
 60        private static byte[] ReadUnsignedInteger(byte[] data, ref int offset, int targetSize = 0)
 61        {
 12862            if (data[offset++] != 0x02)
 63            {
 064                throw new InvalidDataException("Invalid PKCS#8 Data");
 65            }
 66
 12867            int length = ReadLength(data, ref offset);
 68
 69            // Encoding rules say 0 is encoded as the one byte value 0x00.
 70            // Since we expect unsigned, throw if the high bit is set.
 12871            if (length < 1 || data[offset] >= 0x80)
 72            {
 073                throw new InvalidDataException("Invalid PKCS#8 Data");
 74            }
 75
 76            byte[] ret;
 77
 12878            if (length == 1)
 79            {
 080                ret = new byte[length];
 081                ret[0] = data[offset++];
 082                return ret;
 83            }
 84
 12885            if (data[offset] == 0)
 86            {
 6487                offset++;
 6488                length--;
 89            }
 90
 12891            if (targetSize != 0)
 92            {
 9693                if (length > targetSize)
 94                {
 095                    throw new InvalidDataException("Invalid PKCS#8 Data");
 96                }
 97
 9698                ret = new byte[targetSize];
 99            }
 100            else
 101            {
 32102                ret = new byte[length];
 103            }
 104
 128105            Buffer.BlockCopy(data, offset, ret, ret.Length - length, length);
 128106            offset += length;
 128107            return ret;
 108        }
 109
 110        private static void ConsumeFullPayloadTag(byte[] data, ref int offset, byte tagValue)
 111        {
 52112            if (data[offset++] != tagValue)
 113            {
 4114                throw new InvalidDataException("Invalid PKCS#8 Data");
 115            }
 116
 48117            int length = ReadLength(data, ref offset);
 118
 48119            if (data.Length - offset != length)
 120            {
 0121                throw new InvalidDataException("Invalid PKCS#8 Data");
 122            }
 48123        }
 124
 125        private static void ConsumeMatch(byte[] data, ref int offset, byte[] toMatch)
 126        {
 48127            if (data.Length - offset > toMatch.Length)
 128            {
 48129                if (data.Skip(offset).Take(toMatch.Length).SequenceEqual(toMatch))
 130                {
 48131                    offset += toMatch.Length;
 48132                    return;
 133                }
 134            }
 135
 0136            throw new InvalidDataException("Invalid PKCS#8 Data");
 137        }
 138
 139        public static RSA DecodeRSAPkcs8(byte[] pkcs8Bytes)
 140        {
 20141            int offset = 0;
 142
 143            // PrivateKeyInfo SEQUENCE
 20144            ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x30);
 145            // PKCS#8 PrivateKeyInfo.version == 0
 16146            ConsumeMatch(pkcs8Bytes, ref offset, s_derIntegerZero);
 147            // rsaEncryption AlgorithmIdentifier value
 16148            ConsumeMatch(pkcs8Bytes, ref offset, s_rsaAlgorithmId);
 149            // PrivateKeyInfo.privateKey OCTET STRING
 16150            ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x04);
 151            // RSAPrivateKey SEQUENCE
 16152            ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x30);
 153            // RSAPrivateKey.version == 0
 16154            ConsumeMatch(pkcs8Bytes, ref offset, s_derIntegerZero);
 155
 16156            RSAParameters rsaParameters = new RSAParameters();
 16157            rsaParameters.Modulus = ReadUnsignedInteger(pkcs8Bytes, ref offset);
 16158            rsaParameters.Exponent = ReadUnsignedInteger(pkcs8Bytes, ref offset);
 16159            rsaParameters.D = ReadUnsignedInteger(pkcs8Bytes, ref offset, rsaParameters.Modulus.Length);
 16160            int halfModulus = (rsaParameters.Modulus.Length + 1) / 2;
 16161            rsaParameters.P = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
 16162            rsaParameters.Q = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
 16163            rsaParameters.DP = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
 16164            rsaParameters.DQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
 16165            rsaParameters.InverseQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus);
 166
 16167            if (offset != pkcs8Bytes.Length)
 168            {
 0169                throw new InvalidDataException("Invalid PKCS#8 Data");
 170            }
 171
 16172            RSA rsa = RSA.Create();
 16173            rsa.ImportParameters(rsaParameters);
 16174            return rsa;
 175        }
 176    }
 177}