| | 1 | | // Copyright (c) Microsoft Corporation. All rights reserved. |
| | 2 | | // Licensed under the MIT License. |
| | 3 | |
|
| | 4 | | using System; |
| | 5 | | using System.IO; |
| | 6 | | using System.Linq; |
| | 7 | | using System.Security.Cryptography; |
| | 8 | |
|
| | 9 | | namespace 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 | | { |
| 2 | 25 | | private static readonly byte[] s_derIntegerZero = { 0x02, 0x01, 0x00 }; |
| | 26 | |
|
| 2 | 27 | | private static readonly byte[] s_rsaAlgorithmId = |
| 2 | 28 | | { |
| 2 | 29 | | 0x30, 0x0D, |
| 2 | 30 | | 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, |
| 2 | 31 | | 0x05, 0x00, |
| 2 | 32 | | }; |
| | 33 | |
|
| | 34 | | private static int ReadLength(byte[] data, ref int offset) |
| | 35 | | { |
| 176 | 36 | | byte lengthOrLengthLength = data[offset++]; |
| | 37 | |
|
| 176 | 38 | | if (lengthOrLengthLength < 0x80) |
| | 39 | | { |
| 16 | 40 | | return lengthOrLengthLength; |
| | 41 | | } |
| | 42 | |
|
| 160 | 43 | | int lengthLength = lengthOrLengthLength & 0x7F; |
| 160 | 44 | | int length = 0; |
| | 45 | |
|
| 960 | 46 | | for (int i = 0; i < lengthLength; i++) |
| | 47 | | { |
| 320 | 48 | | if (length > ushort.MaxValue) |
| | 49 | | { |
| 0 | 50 | | throw new InvalidDataException("Invalid PKCS#8 Data"); |
| | 51 | | } |
| | 52 | |
|
| 320 | 53 | | length <<= 8; |
| 320 | 54 | | length |= data[offset++]; |
| | 55 | | } |
| | 56 | |
|
| 160 | 57 | | return length; |
| | 58 | | } |
| | 59 | |
|
| | 60 | | private static byte[] ReadUnsignedInteger(byte[] data, ref int offset, int targetSize = 0) |
| | 61 | | { |
| 128 | 62 | | if (data[offset++] != 0x02) |
| | 63 | | { |
| 0 | 64 | | throw new InvalidDataException("Invalid PKCS#8 Data"); |
| | 65 | | } |
| | 66 | |
|
| 128 | 67 | | 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. |
| 128 | 71 | | if (length < 1 || data[offset] >= 0x80) |
| | 72 | | { |
| 0 | 73 | | throw new InvalidDataException("Invalid PKCS#8 Data"); |
| | 74 | | } |
| | 75 | |
|
| | 76 | | byte[] ret; |
| | 77 | |
|
| 128 | 78 | | if (length == 1) |
| | 79 | | { |
| 0 | 80 | | ret = new byte[length]; |
| 0 | 81 | | ret[0] = data[offset++]; |
| 0 | 82 | | return ret; |
| | 83 | | } |
| | 84 | |
|
| 128 | 85 | | if (data[offset] == 0) |
| | 86 | | { |
| 64 | 87 | | offset++; |
| 64 | 88 | | length--; |
| | 89 | | } |
| | 90 | |
|
| 128 | 91 | | if (targetSize != 0) |
| | 92 | | { |
| 96 | 93 | | if (length > targetSize) |
| | 94 | | { |
| 0 | 95 | | throw new InvalidDataException("Invalid PKCS#8 Data"); |
| | 96 | | } |
| | 97 | |
|
| 96 | 98 | | ret = new byte[targetSize]; |
| | 99 | | } |
| | 100 | | else |
| | 101 | | { |
| 32 | 102 | | ret = new byte[length]; |
| | 103 | | } |
| | 104 | |
|
| 128 | 105 | | Buffer.BlockCopy(data, offset, ret, ret.Length - length, length); |
| 128 | 106 | | offset += length; |
| 128 | 107 | | return ret; |
| | 108 | | } |
| | 109 | |
|
| | 110 | | private static void ConsumeFullPayloadTag(byte[] data, ref int offset, byte tagValue) |
| | 111 | | { |
| 52 | 112 | | if (data[offset++] != tagValue) |
| | 113 | | { |
| 4 | 114 | | throw new InvalidDataException("Invalid PKCS#8 Data"); |
| | 115 | | } |
| | 116 | |
|
| 48 | 117 | | int length = ReadLength(data, ref offset); |
| | 118 | |
|
| 48 | 119 | | if (data.Length - offset != length) |
| | 120 | | { |
| 0 | 121 | | throw new InvalidDataException("Invalid PKCS#8 Data"); |
| | 122 | | } |
| 48 | 123 | | } |
| | 124 | |
|
| | 125 | | private static void ConsumeMatch(byte[] data, ref int offset, byte[] toMatch) |
| | 126 | | { |
| 48 | 127 | | if (data.Length - offset > toMatch.Length) |
| | 128 | | { |
| 48 | 129 | | if (data.Skip(offset).Take(toMatch.Length).SequenceEqual(toMatch)) |
| | 130 | | { |
| 48 | 131 | | offset += toMatch.Length; |
| 48 | 132 | | return; |
| | 133 | | } |
| | 134 | | } |
| | 135 | |
|
| 0 | 136 | | throw new InvalidDataException("Invalid PKCS#8 Data"); |
| | 137 | | } |
| | 138 | |
|
| | 139 | | public static RSA DecodeRSAPkcs8(byte[] pkcs8Bytes) |
| | 140 | | { |
| 20 | 141 | | int offset = 0; |
| | 142 | |
|
| | 143 | | // PrivateKeyInfo SEQUENCE |
| 20 | 144 | | ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x30); |
| | 145 | | // PKCS#8 PrivateKeyInfo.version == 0 |
| 16 | 146 | | ConsumeMatch(pkcs8Bytes, ref offset, s_derIntegerZero); |
| | 147 | | // rsaEncryption AlgorithmIdentifier value |
| 16 | 148 | | ConsumeMatch(pkcs8Bytes, ref offset, s_rsaAlgorithmId); |
| | 149 | | // PrivateKeyInfo.privateKey OCTET STRING |
| 16 | 150 | | ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x04); |
| | 151 | | // RSAPrivateKey SEQUENCE |
| 16 | 152 | | ConsumeFullPayloadTag(pkcs8Bytes, ref offset, 0x30); |
| | 153 | | // RSAPrivateKey.version == 0 |
| 16 | 154 | | ConsumeMatch(pkcs8Bytes, ref offset, s_derIntegerZero); |
| | 155 | |
|
| 16 | 156 | | RSAParameters rsaParameters = new RSAParameters(); |
| 16 | 157 | | rsaParameters.Modulus = ReadUnsignedInteger(pkcs8Bytes, ref offset); |
| 16 | 158 | | rsaParameters.Exponent = ReadUnsignedInteger(pkcs8Bytes, ref offset); |
| 16 | 159 | | rsaParameters.D = ReadUnsignedInteger(pkcs8Bytes, ref offset, rsaParameters.Modulus.Length); |
| 16 | 160 | | int halfModulus = (rsaParameters.Modulus.Length + 1) / 2; |
| 16 | 161 | | rsaParameters.P = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); |
| 16 | 162 | | rsaParameters.Q = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); |
| 16 | 163 | | rsaParameters.DP = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); |
| 16 | 164 | | rsaParameters.DQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); |
| 16 | 165 | | rsaParameters.InverseQ = ReadUnsignedInteger(pkcs8Bytes, ref offset, halfModulus); |
| | 166 | |
|
| 16 | 167 | | if (offset != pkcs8Bytes.Length) |
| | 168 | | { |
| 0 | 169 | | throw new InvalidDataException("Invalid PKCS#8 Data"); |
| | 170 | | } |
| | 171 | |
|
| 16 | 172 | | RSA rsa = RSA.Create(); |
| 16 | 173 | | rsa.ImportParameters(rsaParameters); |
| 16 | 174 | | return rsa; |
| | 175 | | } |
| | 176 | | } |
| | 177 | | } |