OpenSSLで生成されたpem形式のx 509証明書を読み込むための完全なクラス
74326 ワード
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("
", "").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 }