Initial commit.
[BoarSSL] / XKeys / KF.cs
1 /*
2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25 using System;
26 using System.IO;
27 using System.Text;
28
29 using Asn1;
30 using Crypto;
31
32 namespace XKeys {
33
34 /*
35 * The KF class contains static methods to decode and encode algorithm
36 * parameters, public keys and private keys.
37 */
38
39 public class KF {
40
41 const string OID_RSA = "1.2.840.113549.1.1.1";
42 const string OID_RSA_OAEP = "1.2.840.113549.1.1.7";
43 const string OID_RSA_PSS = "1.2.840.113549.1.1.10";
44 const string OID_DSA = "1.2.840.10040.4.1";
45 const string OID_EC = "1.2.840.10045.2.1";
46
47 /*
48 * Encode the private key. If 'pk8' is true, then this uses
49 * PKCS#8 format (unencrypted); otherwise, it uses the
50 * "internal" format that does not specifically identify the key
51 * type.
52 */
53 public static byte[] EncodePrivateKey(IPrivateKey sk, bool pk8)
54 {
55 RSAPrivateKey rk = sk as RSAPrivateKey;
56 /* disabled DSA
57 DSAPrivateKey dk = sk as DSAPrivateKey;
58 */
59 ECPrivateKey ek = sk as ECPrivateKey;
60 if (rk != null) {
61 AsnElt ark = AsnElt.Make(AsnElt.SEQUENCE,
62 AsnElt.MakeInteger(0),
63 AsnElt.MakeInteger(rk.N),
64 AsnElt.MakeInteger(rk.E),
65 AsnElt.MakeInteger(rk.D),
66 AsnElt.MakeInteger(rk.P),
67 AsnElt.MakeInteger(rk.Q),
68 AsnElt.MakeInteger(rk.DP),
69 AsnElt.MakeInteger(rk.DQ),
70 AsnElt.MakeInteger(rk.IQ));
71 byte[] enc = ark.Encode();
72 if (pk8) {
73 AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
74 AsnElt.MakeInteger(0),
75 AsnElt.Make(AsnElt.SEQUENCE,
76 AsnElt.MakeOID(OID_RSA),
77 AsnElt.NULL_V),
78 AsnElt.MakeBlob(enc));
79 enc = apk8.Encode();
80 }
81 return enc;
82 /* disabled DSA
83 } else if (dk != null) {
84 if (pk8) {
85 AsnElt adx = AsnElt.MakeInteger(dk.X);
86 AsnElt adp = AsnElt.Make(AsnElt.SEQUENCE,
87 AsnElt.MakeInteger(dk.P),
88 AsnElt.MakeInteger(dk.Q),
89 AsnElt.MakeInteger(dk.G));
90 AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
91 AsnElt.MakeInteger(0),
92 AsnElt.Make(AsnElt.SEQUENCE,
93 AsnElt.MakeOID(OID_DSA),
94 adp),
95 AsnElt.MakeBlob(adx.Encode()));
96 return apk8.Encode();
97 } else {
98 AsnElt adk = AsnElt.Make(AsnElt.SEQUENCE,
99 AsnElt.MakeInteger(0),
100 AsnElt.MakeInteger(dk.P),
101 AsnElt.MakeInteger(dk.Q),
102 AsnElt.MakeInteger(dk.G),
103 AsnElt.MakeInteger(dk.PublicKey.Y),
104 AsnElt.MakeInteger(dk.X));
105 return adk.Encode();
106 }
107 */
108 } else if (ek != null) {
109 /*
110 * The ECPrivateKey class guarantees that the
111 * private key X is already encoded with the same
112 * length as the subgroup order.
113 * The ECPublicKey class provides the public key
114 * as an already encoded point.
115 */
116 AsnElt acc = AsnElt.MakeOID(CurveToOID(ek.Curve));
117 AsnElt apv = AsnElt.MakeExplicit(AsnElt.CONTEXT, 1,
118 AsnElt.MakeBitString(ek.PublicKey.Pub));
119 if (pk8) {
120 AsnElt aek = AsnElt.Make(AsnElt.SEQUENCE,
121 AsnElt.MakeInteger(1),
122 AsnElt.MakeBlob(ek.X),
123 apv);
124 AsnElt apk8 = AsnElt.Make(AsnElt.SEQUENCE,
125 AsnElt.MakeInteger(0),
126 AsnElt.Make(AsnElt.SEQUENCE,
127 AsnElt.MakeOID(OID_EC),
128 acc),
129 AsnElt.MakeBlob(aek.Encode()));
130 return apk8.Encode();
131 } else {
132 AsnElt aek = AsnElt.Make(AsnElt.SEQUENCE,
133 AsnElt.MakeInteger(1),
134 AsnElt.MakeBlob(ek.X),
135 AsnElt.MakeExplicit(
136 AsnElt.CONTEXT, 0, acc),
137 apv);
138 return aek.Encode();
139 }
140 } else {
141 if (sk == null) {
142 throw new NullReferenceException();
143 }
144 throw new ArgumentException("Cannot encode "
145 + sk.AlgorithmName + " private key");
146 }
147 }
148
149 /*
150 * Encode the private key into a PEM object. If 'pk8' is true,
151 * then unencrypted PKCS#8 format is used, and the PEM header
152 * is "BEGIN PRIVATE KEY"; otherwise, the "internal" private key
153 * format is used, and the PEM header identifies the key type.
154 *
155 * If 'crlf' is true, then PEM lines end with CR+LF; otherwise,
156 * they end with LF only.
157 */
158 public static string EncodePrivateKeyPEM(IPrivateKey sk,
159 bool pk8, bool crlf)
160 {
161 byte[] enc = EncodePrivateKey(sk, pk8);
162 string objType;
163 if (pk8) {
164 objType = "PRIVATE KEY";
165 } else {
166 objType = sk.AlgorithmName + " PRIVATE KEY";
167 }
168 return ToPEM(objType, enc, crlf ? "\r\n" : "\n");
169 }
170
171 /*
172 * Decode the provided private key. This method accepts both
173 * PKCS#8 and the "internal" format; the source object may be
174 * raw DER, Base64-encoded DER, or PEM. The key type is
175 * automatically detected.
176 */
177 public static IPrivateKey DecodePrivateKey(byte[] enc)
178 {
179 string pemType;
180 enc = AsnIO.FindBER(enc, false, out pemType);
181 if (enc == null) {
182 throw new AsnException("Not an encoded object");
183 }
184 AsnElt ak = AsnElt.Decode(enc);
185 ak.CheckConstructed();
186 if (pemType != null) {
187 switch (pemType) {
188 case "RSA PRIVATE KEY":
189 return DecodePrivateKeyRSA(ak);
190 /* disabled DSA
191 case "DSA PRIVATE KEY":
192 return DecodePrivateKeyDSA(ak);
193 */
194 case "EC PRIVATE KEY":
195 return DecodePrivateKeyEC(ak);
196 case "PRIVATE KEY":
197 return DecodePrivateKeyPKCS8(ak);
198 default:
199 throw new AsnException(
200 "Unknown PEM object: " + pemType);
201 }
202 }
203 if (ak.Sub.Length == 3
204 && ak.GetSub(0).TagValue == AsnElt.INTEGER
205 && ak.GetSub(1).TagValue == AsnElt.SEQUENCE
206 && ak.GetSub(2).TagValue == AsnElt.OCTET_STRING)
207 {
208 return DecodePrivateKeyPKCS8(ak);
209 }
210 if (ak.Sub.Length >= 9) {
211 bool mayBeRSA = true;
212 for (int i = 0; i < 9; i ++) {
213 if (ak.GetSub(i).TagValue != AsnElt.INTEGER) {
214 mayBeRSA = false;
215 break;
216 }
217 }
218 if (mayBeRSA) {
219 return DecodePrivateKeyRSA(ak);
220 }
221 }
222 /* disabled DSA
223 if (ak.Sub.Length >= 6) {
224 bool mayBeDSA = true;
225 for (int i = 0; i < 6; i ++) {
226 if (ak.GetSub(i).TagValue != AsnElt.INTEGER) {
227 mayBeDSA = false;
228 break;
229 }
230 }
231 if (mayBeDSA) {
232 return DecodePrivateKeyDSA(ak);
233 }
234 }
235 */
236 if (ak.Sub.Length >= 2
237 && ak.GetSub(0).TagValue == AsnElt.INTEGER
238 && ak.GetSub(1).TagValue == AsnElt.OCTET_STRING)
239 {
240 return DecodePrivateKeyEC(ak);
241 }
242 throw new AsnException("Unrecognized private key format");
243 }
244
245 static RSAPrivateKey DecodePrivateKeyRSA(AsnElt ak)
246 {
247 ak.CheckNumSubMin(9);
248 ak.GetSub(0).CheckTag(AsnElt.INTEGER);
249 long kt = ak.GetSub(0).GetInteger();
250 if (kt != 0) {
251 throw new AsnException(
252 "Unsupported RSA key type: " + kt);
253 }
254 ak.CheckNumSub(9);
255 return new RSAPrivateKey(
256 GetPositiveInteger(ak.GetSub(1)),
257 GetPositiveInteger(ak.GetSub(2)),
258 GetPositiveInteger(ak.GetSub(3)),
259 GetPositiveInteger(ak.GetSub(4)),
260 GetPositiveInteger(ak.GetSub(5)),
261 GetPositiveInteger(ak.GetSub(6)),
262 GetPositiveInteger(ak.GetSub(7)),
263 GetPositiveInteger(ak.GetSub(8)));
264 }
265
266 /* disabled DSA
267 static DSAPrivateKey DecodePrivateKeyDSA(AsnElt ak)
268 {
269 ak.CheckNumSubMin(6);
270 for (int i = 0; i < 6; i ++) {
271 ak.GetSub(i).CheckTag(AsnElt.INTEGER);
272 }
273 long kt = ak.GetSub(0).GetInteger();
274 if (kt != 0) {
275 throw new AsnException(
276 "Unsupported DSA key type: " + kt);
277 }
278 DSAPrivateKey dsk = new DSAPrivateKey(
279 GetPositiveInteger(ak.GetSub(1)),
280 GetPositiveInteger(ak.GetSub(2)),
281 GetPositiveInteger(ak.GetSub(3)),
282 GetPositiveInteger(ak.GetSub(5)));
283 DSAPublicKey dpk = dsk.PublicKey;
284 if (BigInt.Compare(dpk.Y,
285 GetPositiveInteger(ak.GetSub(4))) != 0)
286 {
287 throw new CryptoException(
288 "DSA key pair public/private mismatch");
289 }
290 return dsk;
291 }
292 */
293
294 static ECPrivateKey DecodePrivateKeyEC(AsnElt ak)
295 {
296 return DecodePrivateKeyEC(ak, null);
297 }
298
299 static ECPrivateKey DecodePrivateKeyEC(AsnElt ak, ECCurve curve)
300 {
301 ak.CheckNumSubMin(2);
302 ak.GetSub(0).CheckTag(AsnElt.INTEGER);
303 ak.GetSub(1).CheckTag(AsnElt.OCTET_STRING);
304 long kt = ak.GetSub(0).GetInteger();
305 if (kt != 1) {
306 throw new AsnException(
307 "Unsupported EC key type: " + kt);
308 }
309 byte[] x = ak.GetSub(1).CopyValue();
310 byte[] pub = null;
311 int n = ak.Sub.Length;
312 int p = 2;
313 if (p < n) {
314 AsnElt acc = ak.GetSub(p);
315 if (acc.TagClass == AsnElt.CONTEXT
316 && acc.TagValue == 0)
317 {
318 acc.CheckNumSub(1);
319 acc = acc.GetSub(0);
320 ECCurve curve2 = DecodeCurve(acc);
321
322 /*
323 * Here, we support only named curves.
324 */
325 /* obsolete
326 */
327 if (curve == null) {
328 curve = curve2;
329 } else if (!curve.Equals(curve2)) {
330 throw new AsnException(string.Format(
331 "Inconsistent curve"
332 + " specification ({0} / {1})",
333 curve.Name, curve2.Name));
334 }
335
336 p ++;
337 }
338 }
339 if (p < n) {
340 AsnElt acc = ak.GetSub(p);
341 if (acc.TagClass == AsnElt.CONTEXT
342 && acc.TagValue == 1)
343 {
344 acc.CheckNumSub(1);
345 acc = acc.GetSub(0);
346 acc.CheckTag(AsnElt.BIT_STRING);
347 pub = acc.GetBitString();
348 }
349 }
350
351 if (curve == null) {
352 throw new AsnException("No curve specified for EC key");
353 }
354 ECPrivateKey esk = new ECPrivateKey(curve, x);
355 if (pub != null) {
356 ECPublicKey epk = new ECPublicKey(curve, pub);
357 if (!epk.Equals(esk.PublicKey)) {
358 throw new CryptoException(
359 "EC key pair public/private mismatch");
360 }
361 }
362 return esk;
363 }
364
365 static ECCurve DecodeCurve(AsnElt acc)
366 {
367 /*
368 * We support only named curves for now. PKIX does not
369 * want to see any other kind of curve anyway (see RFC
370 * 5480).
371 */
372 acc.CheckTag(AsnElt.OBJECT_IDENTIFIER);
373 string oid = acc.GetOID();
374 return OIDToCurve(oid);
375 }
376
377 static IPrivateKey DecodePrivateKeyPKCS8(AsnElt ak)
378 {
379 ak.CheckNumSub(3);
380 ak.GetSub(0).CheckTag(AsnElt.INTEGER);
381 long v = ak.GetSub(0).GetInteger();
382 if (v != 0) {
383 throw new AsnException(
384 "Unsupported PKCS#8 version: " + v);
385 }
386 AsnElt aai = ak.GetSub(1);
387 aai.CheckTag(AsnElt.SEQUENCE);
388 aai.CheckNumSubMin(1);
389 aai.CheckNumSubMin(2);
390 aai.GetSub(0).CheckTag(AsnElt.OBJECT_IDENTIFIER);
391 string oid = aai.GetSub(0).GetOID();
392 ak.GetSub(2).CheckTag(AsnElt.OCTET_STRING);
393 byte[] rawKey = ak.GetSub(2).CopyValue();
394 AsnElt ark = AsnElt.Decode(rawKey);
395
396 switch (oid) {
397 case OID_RSA:
398 case OID_RSA_OAEP:
399 case OID_RSA_PSS:
400 return DecodePrivateKeyRSA(ark);
401 /* disabled DSA
402 case OID_DSA:
403 return DecodePrivateKeyDSA(ark);
404 */
405 case OID_EC:
406 /*
407 * For elliptic curves, the parameters may
408 * include the curve specification.
409 */
410 ECCurve curve = (aai.Sub.Length == 2)
411 ? DecodeCurve(aai.GetSub(1)) : null;
412 return DecodePrivateKeyEC(ark, curve);
413 default:
414 throw new AsnException(
415 "Unknown PKCS#8 key type: " + oid);
416 }
417 }
418
419 /*
420 * Encode a public key as a SubjectPublicKeyInfo structure.
421 */
422 public static AsnElt EncodePublicKey(IPublicKey pk)
423 {
424 RSAPublicKey rk = pk as RSAPublicKey;
425 /* disabled DSA
426 DSAPublicKey dk = pk as DSAPublicKey;
427 */
428 ECPublicKey ek = pk as ECPublicKey;
429 string oid;
430 AsnElt app;
431 byte[] pkv;
432 if (rk != null) {
433 oid = OID_RSA;
434 app = AsnElt.NULL_V;
435 pkv = AsnElt.Make(AsnElt.SEQUENCE,
436 AsnElt.MakeInteger(rk.Modulus),
437 AsnElt.MakeInteger(rk.Exponent)).Encode();
438 /* disabled DSA
439 } else if (dk != null) {
440 oid = OID_DSA;
441 app = AsnElt.Make(AsnElt.SEQUENCE,
442 AsnElt.MakeInteger(dk.P),
443 AsnElt.MakeInteger(dk.Q),
444 AsnElt.MakeInteger(dk.G));
445 pkv = AsnElt.MakeInteger(dk.Y).Encode();
446 */
447 } else if (ek != null) {
448 oid = OID_EC;
449 app = AsnElt.MakeOID(CurveToOID(ek.Curve));
450 pkv = ek.Pub;
451 } else {
452 throw new ArgumentException(
453 "Cannot encode key type: " + pk.AlgorithmName);
454 }
455 AsnElt ai;
456 if (app == null) {
457 ai = AsnElt.Make(AsnElt.SEQUENCE,
458 AsnElt.MakeOID(oid));
459 } else {
460 ai = AsnElt.Make(AsnElt.SEQUENCE,
461 AsnElt.MakeOID(oid),
462 app);
463 }
464 return AsnElt.Make(AsnElt.SEQUENCE,
465 ai,
466 AsnElt.MakeBitString(pkv));
467 }
468
469 /*
470 * Decode a public key (SubjectPublicKeyInfo).
471 */
472 public static IPublicKey DecodePublicKey(byte[] spki)
473 {
474 string pemType = null;
475 spki = AsnIO.FindBER(spki, false, out pemType);
476 if (spki == null) {
477 throw new AsnException("Not an encoded object");
478 }
479 return DecodePublicKey(AsnElt.Decode(spki));
480 }
481
482 /*
483 * Decode a public key (SubjectPublicKeyInfo).
484 */
485 public static IPublicKey DecodePublicKey(AsnElt ak)
486 {
487 ak.CheckNumSub(2);
488 AlgorithmIdentifier ai = new AlgorithmIdentifier(ak.GetSub(0));
489 AsnElt abs = ak.GetSub(1);
490 abs.CheckTag(AsnElt.BIT_STRING);
491 byte[] pub = abs.GetBitString();
492 switch (ai.OID) {
493 case OID_RSA:
494 case OID_RSA_OAEP:
495 case OID_RSA_PSS:
496 return DecodePublicKeyRSA(pub);
497 /* disabled DSA
498 case OID_DSA:
499 return DecodePublicKeyDSA(pub);
500 */
501 case OID_EC:
502 /*
503 * For elliptic curves, the parameters should
504 * include the curve specification.
505 */
506 AsnElt ap = ai.Parameters;
507 if (ap == null) {
508 throw new AsnException("No curve specified"
509 + " for EC public key");
510 }
511 if (ap.TagClass != AsnElt.UNIVERSAL
512 || ap.TagValue != AsnElt.OBJECT_IDENTIFIER)
513 {
514 throw new AsnException("Unsupported type"
515 + " of curve specification");
516 }
517 return new ECPublicKey(OIDToCurve(ap.GetOID()), pub);
518 default:
519 throw new AsnException(
520 "Unknown public key type: " + ai.OID);
521 }
522 }
523
524 static IPublicKey DecodePublicKeyRSA(byte[] pub)
525 {
526 AsnElt ae = AsnElt.Decode(pub);
527 ae.CheckTag(AsnElt.SEQUENCE);
528 ae.CheckNumSub(2);
529 byte[] n = GetPositiveInteger(ae.GetSub(0));
530 byte[] e = GetPositiveInteger(ae.GetSub(1));
531 return new RSAPublicKey(n, e);
532 }
533
534 static string CurveToOID(ECCurve curve)
535 {
536 switch (curve.Name) {
537 case "P-192":
538 return "1.2.840.10045.3.1.1";
539 case "P-224":
540 return "1.3.132.0.33";
541 case "P-256":
542 return "1.2.840.10045.3.1.7";
543 case "P-384":
544 return "1.3.132.0.34";
545 case "P-521":
546 return "1.3.132.0.35";
547 }
548 throw new ArgumentException(string.Format(
549 "No known OID for curve '{0}'", curve.Name));
550 }
551
552 static ECCurve OIDToCurve(string oid)
553 {
554 switch (oid) {
555 /*
556 case "1.2.840.10045.3.1.1":
557 return NIST.P192;
558 case "1.3.132.0.33":
559 return NIST.P224;
560 */
561 case "1.2.840.10045.3.1.7":
562 return NIST.P256;
563 case "1.3.132.0.34":
564 return NIST.P384;
565 case "1.3.132.0.35":
566 return NIST.P521;
567 }
568 throw new ArgumentException(string.Format(
569 "No known curve for OID {0}", oid));
570 }
571
572 static byte[] GetPositiveInteger(AsnElt ae)
573 {
574 ae.CheckTag(AsnElt.INTEGER);
575 byte[] x = ae.CopyValue();
576 if (x.Length == 0) {
577 throw new AsnException("Invalid integer (empty)");
578 }
579 if (x[0] >= 0x80) {
580 throw new AsnException("Invalid integer (negative)");
581 }
582 return x;
583 }
584
585 static int Dec16be(byte[] buf, int off)
586 {
587 return (buf[off] << 8) + buf[off + 1];
588 }
589
590 static int Dec24be(byte[] buf, int off)
591 {
592 return (buf[off] << 16) + (buf[off + 1] << 8) + buf[off + 2];
593 }
594
595 const string B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
596 + "abcdefghijklmnopqrstuvwxyz0123456789+/";
597
598 public static string ToBase64(byte[] buf, int off, int len)
599 {
600 char[] tc = new char[((len + 2) / 3) << 2];
601 for (int i = 0, j = 0; i < len; i += 3) {
602 if ((i + 3) <= len) {
603 int x = Dec24be(buf, off + i);
604 tc[j ++] = B64[x >> 18];
605 tc[j ++] = B64[(x >> 12) & 0x3F];
606 tc[j ++] = B64[(x >> 6) & 0x3F];
607 tc[j ++] = B64[x & 0x3F];
608 } else if ((i + 2) == len) {
609 int x = Dec16be(buf, off + i);
610 tc[j ++] = B64[x >> 10];
611 tc[j ++] = B64[(x >> 4) & 0x3F];
612 tc[j ++] = B64[(x << 2) & 0x3F];
613 tc[j ++] = '=';
614 } else if ((i + 1) == len) {
615 int x = buf[off + i];
616 tc[j ++] = B64[(x >> 2) & 0x3F];
617 tc[j ++] = B64[(x << 4) & 0x3F];
618 tc[j ++] = '=';
619 tc[j ++] = '=';
620 }
621 }
622 return new string(tc);
623 }
624
625 public static void WritePEM(TextWriter w, string objType, byte[] buf)
626 {
627 w.WriteLine("-----BEGIN {0}-----", objType.ToUpperInvariant());
628 int n = buf.Length;
629 for (int i = 0; i < n; i += 57) {
630 int len = Math.Min(57, n - i);
631 w.WriteLine(ToBase64(buf, i, len));
632 }
633 w.WriteLine("-----END {0}-----", objType.ToUpperInvariant());
634 }
635
636 public static string ToPEM(string objType, byte[] buf)
637 {
638 return ToPEM(objType, buf, "\n");
639 }
640
641 public static string ToPEM(string objType, byte[] buf, string nl)
642 {
643 StringWriter w = new StringWriter();
644 w.NewLine = nl;
645 WritePEM(w, objType, buf);
646 return w.ToString();
647 }
648 }
649
650 }