一個完整的類用來讀取OpenSSL生成的pem格式的x509證書

FrankYou發表於2015-07-21
  1     internal static class CcbRsaHelper
  2     {
  3         private const string Begin = "-----BEGIN ";
  4         private const string End = "-----END ";
  5         private const string Private = "PRIVATE KEY";
  6 
  7         /// <summary>Imports PEM formatted key or certificate into crypto provider</summary>
  8         /// <param name="data">Content of PEM-formatted object.</param>
  9         /// <returns>Crypto provider, defined by given argument.</returns>
 10         internal static RSACryptoServiceProvider FromPem(string data)
 11         {
 12             return FromPem(data, null);
 13         }
 14 
 15         /// <summary>Imports PEM formatted key or certificate into crypto provider</summary>
 16         /// <param name="data">Content of PEM-formatted object.</param>
 17         /// <param name="passKey">Passkey for PEM-formatted object.</param>
 18         /// <returns>Crypto provider, defined by given argument.</returns>
 19         internal static RSACryptoServiceProvider FromPem(string data, string passKey)
 20         {
 21             using (var reader = new StringReader(data))
 22             {
 23                 var line = reader.ReadLine();
 24                 if (line.IsEmpty() || !line.StartsWith(Begin))
 25                 {
 26                     throw new ArgumentException("This is not valid PEM format", "data", new FormatException("PEM start identifier is invalid or not found."));
 27                 }
 28                 line = line.Substring(Begin.Length);
 29                 var idx = line.IndexOf('-');
 30                 if (idx <= 0)
 31                 {
 32                     throw new ArgumentException("This is not valid PEM format", "data", new FormatException("PEM start identifier is invalid or not found."));
 33                 }
 34                 var type = line.Before(idx);
 35                 return LoadPem(reader, type, passKey);
 36             }
 37         }
 38 
 39         internal static RSAParameters FromPemPublicKey(string pemFileConent)
 40         {
 41             if (string.IsNullOrEmpty(pemFileConent))
 42             {
 43                 throw new ArgumentNullException("pemFileConent", "This arg cann't be empty.");
 44             }
 45 
 46             pemFileConent = pemFileConent.Replace("-----BEGIN PUBLIC KEY-----", "").Replace("-----END PUBLIC KEY-----", "").Replace("\n", "").Replace("\r", "");
 47             var keyData = Convert.FromBase64String(pemFileConent);
 48             var keySize1024 = (keyData.Length == 162);
 49             var keySize2048 = (keyData.Length == 294);
 50             if (!(keySize1024 || keySize2048))
 51             {
 52                 throw new ArgumentException("pem file content is incorrect, Only support the key size is 1024 or 2048");
 53             }
 54 
 55             var pemModulus = (keySize1024 ? new byte[128] : new byte[256]);
 56             var pemPublicExponent = new byte[3];
 57             Array.Copy(keyData, (keySize1024 ? 29 : 33), pemModulus, 0, (keySize1024 ? 128 : 256));
 58             Array.Copy(keyData, (keySize1024 ? 159 : 291), pemPublicExponent, 0, 3);
 59             var para = new RSAParameters {Modulus = pemModulus, Exponent = pemPublicExponent};
 60             return para;
 61         }
 62 
 63         private static RSACryptoServiceProvider LoadPem(TextReader reader, string type, string passkey)
 64         {
 65             var end = End + type;
 66             var headers = new PemHeaders();
 67             string line;
 68             var body = new StringBuilder();
 69             while ((line = reader.ReadLine()) != null && line.IndexOf(end, StringComparison.Ordinal) == -1)
 70             {
 71                 var d = line.IndexOf(':');
 72                 if (d >= 0)
 73                 {
 74                     // header
 75                     var n = line.Substring(0, d).Trim();
 76                     if (n.StartsWith("X-")) n = n.Substring(2);
 77                     var v = line.After(d).Trim();
 78                     if (!headers.ContainsKey(n))
 79                     {
 80                         headers.Add(n, v);
 81                     }
 82                     else
 83                     {
 84                         throw new FormatException("Duplicate header {0} in PEM data.".Substitute(n));
 85                     }
 86                 }
 87                 else
 88                 {
 89                     // body
 90                     body.Append(line);
 91                 }
 92             }
 93             if (body.Length%4 != 0)
 94             {
 95                 throw new FormatException("PEM data is invalid or truncated.");
 96             }
 97 
 98             return CreatePem(type, headers, Convert.FromBase64String(body.ToString()), passkey);
 99         }
100 
101         private static RSACryptoServiceProvider CreatePem(string type, PemHeaders headers, byte[] body, string passkey)
102         {
103             if (type.EndsWith(Private))
104             {
105                 return FromPrivateKey(type, headers, body, passkey);
106             }
107             throw new NotSupportedException("Import of {0} is not supported. Only RSA private key import is supported.".Substitute(type));
108         }
109 
110 
111         private static RSACryptoServiceProvider FromPrivateKey(string type, PemHeaders headers, byte[] body, string passkey)
112         {
113             if (type == null) throw new ArgumentNullException("type");
114             var pType = headers.TryGet("Proc-Type");
115             if (pType != "4,ENCRYPTED") return null;
116             if (passkey.IsEmpty())
117             {
118                 throw new ArgumentException("Passkey is mandatory for encrypted PEM object");
119             }
120 
121             var dek = headers.TryGet("DEK-Info");
122             var tkz = dek.Split(',');
123             if (tkz.Length > 1)
124             {
125                 var alg = new Alg(tkz[0]);
126                 var saltLen = tkz[1].Length;
127                 var salt = new byte[saltLen/2];
128                 for (var i = 0; i < saltLen/2; i++)
129                 {
130                     var pair = tkz[1].Substring(2*i, 2);
131                     salt[i] = Byte.Parse(pair, NumberStyles.AllowHexSpecifier);
132                 }
133 
134                 body = DecodePem(body, passkey, alg, salt);
135                 if (body != null)
136                 {
137                     return DecodeRsaPrivateKey(body);
138                 }
139             }
140             else
141             {
142                 throw new FormatException("DEK information is invalid or truncated.");
143             }
144 
145             return null;
146         }
147 
148         private static RSACryptoServiceProvider DecodeRsaPrivateKey(byte[] body)
149         {
150             using (var ms = new MemoryStream(body))
151             {
152                 using (var reader = new BinaryReader(ms))
153                 {
154                     try
155                     {
156                         var tb = reader.ReadUInt16(); // LE: x30 x81
157                         switch (tb)
158                         {
159                             case 0x8130:
160                                 reader.ReadByte(); // fw 1
161                                 break;
162                             case 0x8230:
163                                 reader.ReadInt16(); // fw 2
164                                 break;
165                             default:
166                                 return null;
167                         }
168 
169                         tb = reader.ReadUInt16(); // version
170                         if (tb != 0x0102)
171                         {
172                             return null;
173                         }
174                         if (reader.ReadByte() != 0x00)
175                         {
176                             return null;
177                         }
178 
179                         var modulus = ReadInt(reader);
180                         var e = ReadInt(reader);
181                         var d = ReadInt(reader);
182                         var p = ReadInt(reader);
183                         var q = ReadInt(reader);
184                         var dp = ReadInt(reader);
185                         var dq = ReadInt(reader);
186                         var iq = ReadInt(reader);
187 
188                         var result = new RSACryptoServiceProvider();
189                         var param = new RSAParameters
190                         {
191                             Modulus = modulus,
192                             Exponent = e,
193                             D = d,
194                             P = p,
195                             Q = q,
196                             DP = dp,
197                             DQ = dq,
198                             InverseQ = iq
199                         };
200                         result.ImportParameters(param);
201                         return result;
202                     }
203                     finally
204                     {
205                         reader.Close();
206                     }
207                 }
208             }
209         }
210 
211         private static readonly Func<BinaryReader, byte[]> ReadInt = r =>
212         {
213             var s = GetIntSize(r);
214             return r.ReadBytes(s);
215         };
216 
217         private static readonly Func<BinaryReader, int> GetIntSize = r =>
218         {
219             int c;
220             var b = r.ReadByte();
221             if (b != 0x02)
222             {
223                 //int
224                 return 0;
225             }
226             b = r.ReadByte();
227 
228             switch (b)
229             {
230                 case 0x81:
231                     c = r.ReadByte(); //size
232                     break;
233                 case 0x82:
234                     var hb = r.ReadByte();
235                     var lb = r.ReadByte();
236                     byte[] m = {lb, hb, 0x00, 0x00};
237                     c = BitConverter.ToInt32(m, 0);
238                     break;
239                 default:
240                     c = b; //got size
241                     break;
242             }
243 
244             while (r.ReadByte() == 0x00)
245             {
246                 //remove high zero
247                 c -= 1;
248             }
249             r.BaseStream.Seek(-1, SeekOrigin.Current); // last byte is not zero, go back;
250             return c;
251         };
252 
253         private static byte[] DecodePem(byte[] body, string passkey, Alg alg, byte[] salt)
254         {
255             if (alg == null) throw new ArgumentNullException("alg");
256             if (alg.AlgBase != Alg.BaseAlg.DES_EDE3 && alg.AlgMode != Alg.Mode.CBC)
257             {
258                 throw new NotSupportedException("Only 3DES-CBC keys are supported.");
259             }
260             var des = Get3DesKey(salt, passkey);
261             if (des == null)
262             {
263                 throw new ApplicationException("Unable to calculate 3DES key for decryption.");
264             }
265             var rsa = DecryptRsaKey(body, des, salt);
266             if (rsa == null)
267             {
268                 throw new ApplicationException("Unable to decrypt RSA private key.");
269             }
270             return rsa;
271         }
272 
273         private static byte[] DecryptRsaKey(byte[] body, byte[] desKey, byte[] iv)
274         {
275             byte[] result;
276             using (var stream = new MemoryStream())
277             {
278                 var alg = TripleDES.Create();
279                 alg.Key = desKey;
280                 alg.IV = iv;
281                 using (var cs = new CryptoStream(stream, alg.CreateDecryptor(), CryptoStreamMode.Write))
282                 {
283                     cs.Write(body, 0, body.Length);
284                     cs.Close();
285                 }
286                 result = stream.ToArray();
287             }
288             return result;
289         }
290 
291         private static byte[] Get3DesKey(byte[] salt, string passkey)
292         {
293             const int hashlength = 16;
294             const int m = 2; // 2 iterations for at least 24 bytes
295             const int c = 1; // 1 hash for Open SSL
296             var k = new byte[hashlength*m];
297 
298             var pk = Encoding.ASCII.GetBytes(passkey);
299             var data = new byte[salt.Length + pk.Length];
300             Array.Copy(pk, data, pk.Length);
301             Array.Copy(salt, 0, data, pk.Length, salt.Length);
302             var md5 = new MD5CryptoServiceProvider();
303             byte[] result = null;
304             var hash = new byte[hashlength + data.Length];
305 
306             for (var i = 0; i < m; i++)
307             {
308                 if (i == 0)
309                 {
310                     result = data;
311                 }
312                 else
313                 {
314                     Array.Copy(result, hash, result.Length);
315                     Array.Copy(data, 0, hash, result.Length, data.Length);
316                     result = hash;
317                 }
318 
319                 for (var j = 0; j < c; j++)
320                 {
321                     result = md5.ComputeHash(result);
322                 }
323                 Array.Copy(result, 0, k, i*hashlength, result.Length);
324             }
325             var dk = new byte[24]; //final key
326             Array.Copy(k, dk, dk.Length);
327             return dk;
328         }
329 
330         private class PemHeaders : Dictionary<string, string>
331         {
332         }
333 
334         private sealed class Alg
335         {
336             public Alg(string alg)
337             {
338                 AlgMode = Mode.ECB;
339                 switch (alg.Trim())
340                 {
341                     //TK: DES-EDE based algorithms come only with ECB mode.
342                     case "DES-EDE":
343                         AlgBase = BaseAlg.DES_EDE;
344                         return;
345                     case "DES-EDE3":
346                         AlgBase = BaseAlg.DES_EDE3;
347                         return;
348                     default:
349                         var p = alg.LastIndexOf('-');
350                         if (p >= 0)
351                         {
352                             AlgBase = (BaseAlg) _parseVal(typeof (BaseAlg), alg.Before(p));
353                             AlgMode = (Mode) _parseVal(typeof (Mode), alg.After(p));
354                             return;
355                         }
356                         break;
357                 }
358                 throw new ArgumentException("Unknown DEK algorithm '{0}'", alg);
359             }
360 
361             public BaseAlg AlgBase { get; private set; }
362 
363             public Mode AlgMode { get; private set; }
364 
365             private readonly Func<Type, string, Enum> _parseVal = (t, s) =>
366             {
367                 s = s.Replace('-', '_');
368                 return (Enum) Enum.Parse(t, s);
369             };
370 
371             public enum BaseAlg
372             {
373                 DES_EDE,
374                 DES_EDE3
375             };
376 
377             public enum Mode
378             {
379                 CBC,
380                 ECB
381             };
382         }
383     }
384 
385     internal static class Helper
386     {
387         public static bool IsEmpty(this string value)
388         {
389             return String.IsNullOrWhiteSpace(value);
390         }
391 
392         public static string Before(this string value, int end)
393         {
394             return (end == 0 ? String.Empty : value.Between(0, end - 1));
395         }
396 
397         public static string After(this string value, int start)
398         {
399             return value.Between(start + 1, Int32.MaxValue);
400         }
401 
402         public static string Between(this string value, int start, int end)
403         {
404             var len = (String.IsNullOrEmpty(value) ? 0 : value.Length);
405             if (start < 0) start += len;
406             if (end < 0) end += len;
407             if (len == 0 || start > len - 1 || end < start)
408             {
409                 return String.Empty;
410             }
411             if (start < 0) start = 0;
412             if (end >= len) end = len - 1;
413             return value.Substring(start, end - start + 1);
414         }
415 
416         public static string Substitute(this string format, params object[] args)
417         {
418             var value = String.Empty;
419             if (String.IsNullOrEmpty(format)) return value;
420             if (args.Length == 0) return format;
421             try
422             {
423                 return String.Format(format, args);
424             }
425             catch (FormatException)
426             {
427                 return format;
428             }
429             catch
430             {
431                 return "***";
432             }
433         }
434 
435         public static TV TryGet<TK, TV>(this Dictionary<TK, TV> dictionary, TK key)
436         {
437             return dictionary.TryGet(key, default(TV));
438         }
439 
440         public static TV TryGet<TK, TV>(this Dictionary<TK, TV> dictionary, TK key, TV defaultValue)
441         {
442             if (dictionary != null && dictionary.ContainsKey(key))
443             {
444                 return dictionary[key];
445             }
446             return defaultValue;
447         }
448     }

 

相關文章