55using System . IO ;
66using System . Linq ;
77using System . Security . Cryptography ;
8+ using System . Text ;
89
910namespace Azure . Core
1011{
@@ -20,7 +21,7 @@ namespace Azure.Core
2021 ///
2122 /// This code is able to decode RSA keys (without any attributes) from well formed PKCS#8 blobs.
2223 /// </summary>
23- internal class LightweightPkcs8Decoder
24+ internal static partial class LightweightPkcs8Decoder
2425 {
2526 private static readonly byte [ ] s_derIntegerZero = { 0x02 , 0x01 , 0x00 } ;
2627
@@ -31,6 +32,159 @@ internal class LightweightPkcs8Decoder
3132 0x05 , 0x00 ,
3233 } ;
3334
35+ internal static byte [ ] ReadBitString ( byte [ ] data , ref int offset )
36+ {
37+ // Adapted from https://github.com/dotnet/runtime/blob/be74b4bd/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.BitString.cs#L156
38+
39+ if ( data [ offset ++ ] != 0x03 )
40+ {
41+ throw new InvalidDataException ( "Invalid PKCS#8 Data" ) ;
42+ }
43+
44+ int length = ReadLength ( data , ref offset ) ;
45+ if ( length == 0 )
46+ {
47+ throw new InvalidDataException ( "Invalid PKCS#8 Data" ) ;
48+ }
49+
50+ int unusedBitCount = data [ offset ++ ] ;
51+ if ( unusedBitCount > 7 )
52+ {
53+ throw new InvalidDataException ( "Invalid PKCS#8 Data" ) ;
54+ }
55+
56+ Span < byte > span = data . AsSpan ( offset , length - 1 ) ;
57+
58+ // Build a mask for the bits that are used so the normalized value can be computed
59+ //
60+ // If 3 bits are "unused" then build a mask for them to check for 0.
61+ // -1 << 3 => 0b1111_1111 << 3 => 0b1111_1000
62+ int mask = - 1 << unusedBitCount ;
63+ byte lastByte = span [ span . Length - 1 ] ;
64+ byte maskedByte = ( byte ) ( lastByte & mask ) ;
65+
66+ byte [ ] ret = new byte [ span . Length ] ;
67+
68+ Buffer . BlockCopy ( data , offset , ret , 0 , span . Length ) ;
69+ ret [ span . Length - 1 ] = maskedByte ;
70+
71+ offset += span . Length ;
72+
73+ return ret ;
74+ }
75+
76+ internal static string ReadObjectIdentifier ( byte [ ] data , ref int offset )
77+ {
78+ // Adapted from https://github.com/dotnet/runtime/blob/be74b4bd/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Oid.cs#L175
79+
80+ if ( data [ offset ++ ] != 0x06 )
81+ {
82+ throw new InvalidDataException ( "Invalid PKCS#8 Data" ) ;
83+ }
84+
85+ int length = ReadLength ( data , ref offset ) ;
86+
87+ StringBuilder ret = new StringBuilder ( ) ;
88+ for ( int i = offset ; i < offset + length ; i ++ )
89+ {
90+ byte val = data [ i ] ;
91+
92+ if ( i == offset )
93+ {
94+ byte first ;
95+ if ( val < 40 )
96+ {
97+ first = 0 ;
98+ }
99+ else if ( val < 80 )
100+ {
101+ first = 1 ;
102+ val -= 40 ;
103+ }
104+ else
105+ {
106+ throw new InvalidDataException ( "Unsupported PKCS#8 Data" ) ;
107+ }
108+
109+ ret . Append ( first ) . Append ( '.' ) . Append ( val ) ;
110+ }
111+ else
112+ {
113+ if ( val < 128 )
114+ {
115+ ret . Append ( '.' ) . Append ( val ) ;
116+ }
117+ else
118+ {
119+ ret . Append ( '.' ) ;
120+
121+ if ( val == 0x80 )
122+ {
123+ throw new InvalidDataException ( "Invalid PKCS#8 Data" ) ;
124+ }
125+
126+ // See how long the segment is.
127+ int end = - 1 ;
128+ int idx ;
129+
130+ for ( idx = i ; idx < offset + length ; idx ++ )
131+ {
132+ if ( ( data [ idx ] & 0x80 ) == 0 )
133+ {
134+ end = idx ;
135+ break ;
136+ }
137+ }
138+
139+ if ( end < 0 )
140+ {
141+ throw new InvalidDataException ( "Invalid PKCS#8 Data" ) ;
142+ }
143+
144+ // 4 or fewer bytes fits into a signed integer.
145+ int max = end + 1 ;
146+ if ( max <= i + 4 )
147+ {
148+ int accum = 0 ;
149+ for ( idx = i ; idx < max ; idx ++ )
150+ {
151+ val = data [ idx ] ;
152+ accum <<= 7 ;
153+ accum |= ( byte ) ( val & 0x7f ) ;
154+ }
155+
156+ ret . Append ( accum ) ;
157+ i = end ;
158+ }
159+ else
160+ {
161+ throw new InvalidDataException ( "Unsupported PKCS#8 Data" ) ;
162+ }
163+ }
164+ }
165+ }
166+
167+ offset += length ;
168+ return ret . ToString ( ) ;
169+ }
170+
171+ internal static byte [ ] ReadOctetString ( byte [ ] data , ref int offset )
172+ {
173+ if ( data [ offset ++ ] != 0x04 )
174+ {
175+ throw new InvalidDataException ( "Invalid PKCS#8 Data" ) ;
176+ }
177+
178+ int length = ReadLength ( data , ref offset ) ;
179+
180+ byte [ ] ret = new byte [ length ] ;
181+
182+ Buffer . BlockCopy ( data , offset , ret , 0 , length ) ;
183+ offset += length ;
184+
185+ return ret ;
186+ }
187+
34188 private static int ReadLength ( byte [ ] data , ref int offset )
35189 {
36190 byte lengthOrLengthLength = data [ offset ++ ] ;
@@ -45,13 +199,13 @@ private static int ReadLength(byte[] data, ref int offset)
45199
46200 for ( int i = 0 ; i < lengthLength ; i ++ )
47201 {
202+ length <<= 8 ;
203+ length |= data [ offset ++ ] ;
204+
48205 if ( length > ushort . MaxValue )
49206 {
50207 throw new InvalidDataException ( "Invalid PKCS#8 Data" ) ;
51208 }
52-
53- length <<= 8 ;
54- length |= data [ offset ++ ] ;
55209 }
56210
57211 return length ;
@@ -107,6 +261,16 @@ private static byte[] ReadUnsignedInteger(byte[] data, ref int offset, int targe
107261 return ret ;
108262 }
109263
264+ private static int ReadPayloadTagLength ( byte [ ] data , ref int offset , byte tagValue )
265+ {
266+ if ( data [ offset ++ ] != tagValue )
267+ {
268+ throw new InvalidDataException ( "Invalid PKCS#8 Data" ) ;
269+ }
270+
271+ return ReadLength ( data , ref offset ) ;
272+ }
273+
110274 private static void ConsumeFullPayloadTag ( byte [ ] data , ref int offset , byte tagValue )
111275 {
112276 if ( data [ offset ++ ] != tagValue )
@@ -173,5 +337,22 @@ public static RSA DecodeRSAPkcs8(byte[] pkcs8Bytes)
173337 rsa . ImportParameters ( rsaParameters ) ;
174338 return rsa ;
175339 }
340+
341+ public static string DecodePrivateKeyOid ( byte [ ] pkcs8Bytes )
342+ {
343+ int offset = 0 ;
344+
345+ // PrivateKeyInfo SEQUENCE
346+ ConsumeFullPayloadTag ( pkcs8Bytes , ref offset , 0x30 ) ;
347+
348+ // PKCS#8 PrivateKeyInfo.version == 0
349+ ConsumeMatch ( pkcs8Bytes , ref offset , s_derIntegerZero ) ;
350+
351+ // PKCS#8 PrivateKeyInfo.sequence
352+ ReadPayloadTagLength ( pkcs8Bytes , ref offset , 0x30 ) ;
353+
354+ // Return the AlgorithmIdentifier value
355+ return ReadObjectIdentifier ( pkcs8Bytes , ref offset ) ;
356+ }
176357 }
177358}
0 commit comments