|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include "secutil.h" |
|
6 #include "plgetopt.h" |
|
7 #include "cert.h" |
|
8 #include "secoid.h" |
|
9 #include "cryptohi.h" |
|
10 |
|
11 /* maximum supported modulus length in bits (indicate problem if over this) */ |
|
12 #define MAX_MODULUS (1024) |
|
13 |
|
14 |
|
15 static void Usage(char *progName) |
|
16 { |
|
17 fprintf(stderr, "Usage: %s [aAvf] [certtocheck] [issuingcert]\n", |
|
18 progName); |
|
19 fprintf(stderr, "%-20s Cert to check is base64 encoded\n", |
|
20 "-a"); |
|
21 fprintf(stderr, "%-20s Issuer's cert is base64 encoded\n", |
|
22 "-A"); |
|
23 fprintf(stderr, "%-20s Verbose (indicate decoding progress etc.)\n", |
|
24 "-v"); |
|
25 fprintf(stderr, "%-20s Force sanity checks even if pretty print fails.\n", |
|
26 "-f"); |
|
27 fprintf(stderr, "%-20s Define an output file to use (default is stdout)\n", |
|
28 "-o output"); |
|
29 fprintf(stderr, "%-20s Specify the input type (no default)\n", |
|
30 "-t type"); |
|
31 exit(-1); |
|
32 } |
|
33 |
|
34 |
|
35 /* |
|
36 * Check integer field named fieldName, printing out results and |
|
37 * returning the length of the integer in bits |
|
38 */ |
|
39 |
|
40 static |
|
41 int checkInteger(SECItem *intItem, char *fieldName, int verbose) |
|
42 { |
|
43 int len, bitlen; |
|
44 if (verbose) { |
|
45 printf("Checking %s\n", fieldName); |
|
46 } |
|
47 |
|
48 len = intItem->len; |
|
49 |
|
50 if (len && (intItem->data[0] & 0x80)) { |
|
51 printf("PROBLEM: %s is NEGATIVE 2's-complement integer.\n", |
|
52 fieldName); |
|
53 } |
|
54 |
|
55 |
|
56 /* calculate bit length and check for unnecessary leading zeros */ |
|
57 bitlen = len << 3; |
|
58 if (len > 1 && intItem->data[0] == 0) { |
|
59 /* leading zero byte(s) */ |
|
60 if (!(intItem->data[1] & 0x80)) { |
|
61 printf("PROBLEM: %s has unneeded leading zeros. Violates DER.\n", |
|
62 fieldName); |
|
63 } |
|
64 /* strip leading zeros in length calculation */ |
|
65 { |
|
66 int i=0; |
|
67 while (bitlen > 8 && intItem->data[i] == 0) { |
|
68 bitlen -= 8; |
|
69 i++; |
|
70 } |
|
71 } |
|
72 } |
|
73 return bitlen; |
|
74 } |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 static |
|
80 void checkName(CERTName *n, char *fieldName, int verbose) |
|
81 { |
|
82 char *v=0; |
|
83 if (verbose) { |
|
84 printf("Checking %s\n", fieldName); |
|
85 } |
|
86 |
|
87 v = CERT_GetCountryName(n); |
|
88 if (!v) { |
|
89 printf("PROBLEM: %s lacks Country Name (C)\n", |
|
90 fieldName); |
|
91 } |
|
92 PORT_Free(v); |
|
93 |
|
94 v = CERT_GetOrgName(n); |
|
95 if (!v) { |
|
96 printf("PROBLEM: %s lacks Organization Name (O)\n", |
|
97 fieldName); |
|
98 } |
|
99 PORT_Free(v); |
|
100 |
|
101 v = CERT_GetOrgUnitName(n); |
|
102 if (!v) { |
|
103 printf("WARNING: %s lacks Organization Unit Name (OU)\n", |
|
104 fieldName); |
|
105 } |
|
106 PORT_Free(v); |
|
107 |
|
108 v = CERT_GetCommonName(n); |
|
109 if (!v) { |
|
110 printf("PROBLEM: %s lacks Common Name (CN)\n", |
|
111 fieldName); |
|
112 } |
|
113 PORT_Free(v); |
|
114 } |
|
115 |
|
116 |
|
117 static |
|
118 SECStatus |
|
119 OurVerifyData(unsigned char *buf, int len, SECKEYPublicKey *key, |
|
120 SECItem *sig, SECAlgorithmID *sigAlgorithm) |
|
121 { |
|
122 SECStatus rv; |
|
123 VFYContext *cx; |
|
124 SECOidData *sigAlgOid, *oiddata; |
|
125 SECOidTag sigAlgTag; |
|
126 SECOidTag hashAlgTag; |
|
127 int showDigestOid=0; |
|
128 |
|
129 cx = VFY_CreateContextWithAlgorithmID(key, sig, sigAlgorithm, &hashAlgTag, |
|
130 NULL); |
|
131 if (cx == NULL) |
|
132 return SECFailure; |
|
133 |
|
134 sigAlgOid = SECOID_FindOID(&sigAlgorithm->algorithm); |
|
135 if (sigAlgOid == 0) |
|
136 return SECFailure; |
|
137 sigAlgTag = sigAlgOid->offset; |
|
138 |
|
139 |
|
140 if (showDigestOid) { |
|
141 oiddata = SECOID_FindOIDByTag(hashAlgTag); |
|
142 if ( oiddata ) { |
|
143 printf("PROBLEM: (cont) Digest OID is %s\n", oiddata->desc); |
|
144 } else { |
|
145 SECU_PrintAsHex(stdout, |
|
146 &oiddata->oid, "PROBLEM: UNKNOWN OID", 0); |
|
147 } |
|
148 } |
|
149 |
|
150 rv = VFY_Begin(cx); |
|
151 if (rv == SECSuccess) { |
|
152 rv = VFY_Update(cx, buf, len); |
|
153 if (rv == SECSuccess) |
|
154 rv = VFY_End(cx); |
|
155 } |
|
156 |
|
157 VFY_DestroyContext(cx, PR_TRUE); |
|
158 return rv; |
|
159 } |
|
160 |
|
161 |
|
162 |
|
163 static |
|
164 SECStatus |
|
165 OurVerifySignedData(CERTSignedData *sd, CERTCertificate *cert) |
|
166 { |
|
167 SECItem sig; |
|
168 SECKEYPublicKey *pubKey = 0; |
|
169 SECStatus rv; |
|
170 |
|
171 /* check the certificate's validity */ |
|
172 rv = CERT_CertTimesValid(cert); |
|
173 if ( rv ) { |
|
174 return(SECFailure); |
|
175 } |
|
176 |
|
177 /* get cert's public key */ |
|
178 pubKey = CERT_ExtractPublicKey(cert); |
|
179 if ( !pubKey ) { |
|
180 return(SECFailure); |
|
181 } |
|
182 |
|
183 /* check the signature */ |
|
184 sig = sd->signature; |
|
185 DER_ConvertBitString(&sig); |
|
186 rv = OurVerifyData(sd->data.data, sd->data.len, pubKey, &sig, |
|
187 &sd->signatureAlgorithm); |
|
188 |
|
189 SECKEY_DestroyPublicKey(pubKey); |
|
190 |
|
191 if ( rv ) { |
|
192 return(SECFailure); |
|
193 } |
|
194 |
|
195 return(SECSuccess); |
|
196 } |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 static |
|
202 CERTCertificate *createEmptyCertificate(void) |
|
203 { |
|
204 PLArenaPool *arena = 0; |
|
205 CERTCertificate *c = 0; |
|
206 |
|
207 arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
|
208 if ( !arena ) { |
|
209 return 0; |
|
210 } |
|
211 |
|
212 |
|
213 c = (CERTCertificate *) PORT_ArenaZAlloc(arena, sizeof(CERTCertificate)); |
|
214 |
|
215 if (c) { |
|
216 c->referenceCount = 1; |
|
217 c->arena = arena; |
|
218 } else { |
|
219 PORT_FreeArena(arena,PR_TRUE); |
|
220 } |
|
221 |
|
222 return c; |
|
223 } |
|
224 |
|
225 |
|
226 |
|
227 |
|
228 int main(int argc, char **argv) |
|
229 { |
|
230 int rv, verbose=0, force=0; |
|
231 int ascii=0, issuerAscii=0; |
|
232 char *progName=0; |
|
233 PRFileDesc *inFile=0, *issuerCertFile=0; |
|
234 SECItem derCert, derIssuerCert; |
|
235 PLArenaPool *arena=0; |
|
236 CERTSignedData *signedData=0; |
|
237 CERTCertificate *cert=0, *issuerCert=0; |
|
238 SECKEYPublicKey *rsapubkey=0; |
|
239 SECAlgorithmID md5WithRSAEncryption, md2WithRSAEncryption; |
|
240 SECAlgorithmID sha1WithRSAEncryption, rsaEncryption; |
|
241 SECItem spk; |
|
242 int selfSigned=0; |
|
243 int invalid=0; |
|
244 char *inFileName = NULL, *issuerCertFileName = NULL; |
|
245 PLOptState *optstate; |
|
246 PLOptStatus status; |
|
247 |
|
248 PORT_Memset(&md5WithRSAEncryption, 0, sizeof(md5WithRSAEncryption)); |
|
249 PORT_Memset(&md2WithRSAEncryption, 0, sizeof(md2WithRSAEncryption)); |
|
250 PORT_Memset(&sha1WithRSAEncryption, 0, sizeof(sha1WithRSAEncryption)); |
|
251 PORT_Memset(&rsaEncryption, 0, sizeof(rsaEncryption)); |
|
252 |
|
253 progName = strrchr(argv[0], '/'); |
|
254 progName = progName ? progName+1 : argv[0]; |
|
255 |
|
256 optstate = PL_CreateOptState(argc, argv, "aAvf"); |
|
257 while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { |
|
258 switch (optstate->option) { |
|
259 case 'v': |
|
260 verbose = 1; |
|
261 break; |
|
262 |
|
263 case 'f': |
|
264 force = 1; |
|
265 break; |
|
266 |
|
267 case 'a': |
|
268 ascii = 1; |
|
269 break; |
|
270 |
|
271 case 'A': |
|
272 issuerAscii = 1; |
|
273 break; |
|
274 |
|
275 case '\0': |
|
276 if (!inFileName) |
|
277 inFileName = PL_strdup(optstate->value); |
|
278 else if (!issuerCertFileName) |
|
279 issuerCertFileName = PL_strdup(optstate->value); |
|
280 else |
|
281 Usage(progName); |
|
282 break; |
|
283 } |
|
284 } |
|
285 |
|
286 if (!inFileName || !issuerCertFileName || status == PL_OPT_BAD) { |
|
287 /* insufficient or excess args */ |
|
288 Usage(progName); |
|
289 } |
|
290 |
|
291 inFile = PR_Open(inFileName, PR_RDONLY, 0); |
|
292 if (!inFile) { |
|
293 fprintf(stderr, "%s: unable to open \"%s\" for reading\n", |
|
294 progName, inFileName); |
|
295 exit(1); |
|
296 } |
|
297 |
|
298 issuerCertFile = PR_Open(issuerCertFileName, PR_RDONLY, 0); |
|
299 if (!issuerCertFile) { |
|
300 fprintf(stderr, "%s: unable to open \"%s\" for reading\n", |
|
301 progName, issuerCertFileName); |
|
302 exit(1); |
|
303 } |
|
304 |
|
305 if (SECU_ReadDERFromFile(&derCert, inFile, ascii, PR_FALSE) != SECSuccess) { |
|
306 printf("Couldn't read input certificate as DER binary or base64\n"); |
|
307 exit(1); |
|
308 } |
|
309 |
|
310 arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
|
311 if (arena == 0) { |
|
312 fprintf(stderr,"%s: can't allocate scratch arena!", progName); |
|
313 exit(1); |
|
314 } |
|
315 |
|
316 if (issuerCertFile) { |
|
317 CERTSignedData *issuerCertSD=0; |
|
318 if (SECU_ReadDERFromFile(&derIssuerCert, issuerCertFile, issuerAscii, |
|
319 PR_FALSE) != SECSuccess) { |
|
320 printf("Couldn't read issuer certificate as DER binary or base64.\n"); |
|
321 exit(1); |
|
322 } |
|
323 issuerCertSD = PORT_ArenaZNew(arena, CERTSignedData); |
|
324 if (!issuerCertSD) { |
|
325 fprintf(stderr,"%s: can't allocate issuer signed data!", progName); |
|
326 exit(1); |
|
327 } |
|
328 rv = SEC_ASN1DecodeItem(arena, issuerCertSD, |
|
329 SEC_ASN1_GET(CERT_SignedDataTemplate), |
|
330 &derIssuerCert); |
|
331 if (rv) { |
|
332 fprintf(stderr, "%s: Issuer cert isn't X509 SIGNED Data?\n", |
|
333 progName); |
|
334 exit(1); |
|
335 } |
|
336 issuerCert = createEmptyCertificate(); |
|
337 if (!issuerCert) { |
|
338 printf("%s: can't allocate space for issuer cert.", progName); |
|
339 exit(1); |
|
340 } |
|
341 rv = SEC_ASN1DecodeItem(arena, issuerCert, |
|
342 SEC_ASN1_GET(CERT_CertificateTemplate), |
|
343 &issuerCertSD->data); |
|
344 if (rv) { |
|
345 printf("%s: Does not appear to be an X509 Certificate.\n", |
|
346 progName); |
|
347 exit(1); |
|
348 } |
|
349 } |
|
350 |
|
351 signedData = PORT_ArenaZNew(arena,CERTSignedData); |
|
352 if (!signedData) { |
|
353 fprintf(stderr,"%s: can't allocate signedData!", progName); |
|
354 exit(1); |
|
355 } |
|
356 |
|
357 rv = SEC_ASN1DecodeItem(arena, signedData, |
|
358 SEC_ASN1_GET(CERT_SignedDataTemplate), |
|
359 &derCert); |
|
360 if (rv) { |
|
361 fprintf(stderr, "%s: Does not appear to be X509 SIGNED Data.\n", |
|
362 progName); |
|
363 exit(1); |
|
364 } |
|
365 |
|
366 if (verbose) { |
|
367 printf("Decoded ok as X509 SIGNED data.\n"); |
|
368 } |
|
369 |
|
370 cert = createEmptyCertificate(); |
|
371 if (!cert) { |
|
372 fprintf(stderr, "%s: can't allocate cert", progName); |
|
373 exit(1); |
|
374 } |
|
375 |
|
376 rv = SEC_ASN1DecodeItem(arena, cert, |
|
377 SEC_ASN1_GET(CERT_CertificateTemplate), |
|
378 &signedData->data); |
|
379 if (rv) { |
|
380 fprintf(stderr, "%s: Does not appear to be an X509 Certificate.\n", |
|
381 progName); |
|
382 exit(1); |
|
383 } |
|
384 |
|
385 |
|
386 if (verbose) { |
|
387 printf("Decoded ok as an X509 certificate.\n"); |
|
388 } |
|
389 |
|
390 SECU_RegisterDynamicOids(); |
|
391 rv = SECU_PrintSignedData(stdout, &derCert, "Certificate", 0, |
|
392 SECU_PrintCertificate); |
|
393 |
|
394 if (rv) { |
|
395 fprintf(stderr, "%s: Unable to pretty print cert. Error: %d\n", |
|
396 progName, PORT_GetError()); |
|
397 if (!force) { |
|
398 exit(1); |
|
399 } |
|
400 } |
|
401 |
|
402 |
|
403 /* Do various checks on the cert */ |
|
404 |
|
405 printf("\n"); |
|
406 |
|
407 /* Check algorithms */ |
|
408 SECOID_SetAlgorithmID(arena, &md5WithRSAEncryption, |
|
409 SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, NULL); |
|
410 |
|
411 SECOID_SetAlgorithmID(arena, &md2WithRSAEncryption, |
|
412 SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION, NULL); |
|
413 |
|
414 SECOID_SetAlgorithmID(arena, &sha1WithRSAEncryption, |
|
415 SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, NULL); |
|
416 |
|
417 SECOID_SetAlgorithmID(arena, &rsaEncryption, |
|
418 SEC_OID_PKCS1_RSA_ENCRYPTION, NULL); |
|
419 |
|
420 { |
|
421 int isMD5RSA = (SECOID_CompareAlgorithmID(&cert->signature, |
|
422 &md5WithRSAEncryption) == 0); |
|
423 int isMD2RSA = (SECOID_CompareAlgorithmID(&cert->signature, |
|
424 &md2WithRSAEncryption) == 0); |
|
425 int isSHA1RSA = (SECOID_CompareAlgorithmID(&cert->signature, |
|
426 &sha1WithRSAEncryption) == 0); |
|
427 |
|
428 if (verbose) { |
|
429 printf("\nDoing algorithm checks.\n"); |
|
430 } |
|
431 |
|
432 if (!(isMD5RSA || isMD2RSA || isSHA1RSA)) { |
|
433 printf("PROBLEM: Signature not PKCS1 MD5, MD2, or SHA1 + RSA.\n"); |
|
434 } else if (!isMD5RSA) { |
|
435 printf("WARNING: Signature not PKCS1 MD5 with RSA Encryption\n"); |
|
436 } |
|
437 |
|
438 if (SECOID_CompareAlgorithmID(&cert->signature, |
|
439 &signedData->signatureAlgorithm)) { |
|
440 printf("PROBLEM: Algorithm in sig and certInfo don't match.\n"); |
|
441 } |
|
442 } |
|
443 |
|
444 if (SECOID_CompareAlgorithmID(&cert->subjectPublicKeyInfo.algorithm, |
|
445 &rsaEncryption)) { |
|
446 printf("PROBLEM: Public key algorithm is not PKCS1 RSA Encryption.\n"); |
|
447 } |
|
448 |
|
449 /* Check further public key properties */ |
|
450 spk = cert->subjectPublicKeyInfo.subjectPublicKey; |
|
451 DER_ConvertBitString(&spk); |
|
452 |
|
453 if (verbose) { |
|
454 printf("\nsubjectPublicKey DER\n"); |
|
455 rv = DER_PrettyPrint(stdout, &spk, PR_FALSE); |
|
456 printf("\n"); |
|
457 } |
|
458 |
|
459 rsapubkey = (SECKEYPublicKey *) |
|
460 PORT_ArenaZAlloc(arena,sizeof(SECKEYPublicKey)); |
|
461 if (!rsapubkey) { |
|
462 fprintf(stderr, "%s: rsapubkey allocation failed.\n", progName); |
|
463 exit(1); |
|
464 } |
|
465 |
|
466 rv = SEC_ASN1DecodeItem(arena, rsapubkey, |
|
467 SEC_ASN1_GET(SECKEY_RSAPublicKeyTemplate), &spk); |
|
468 if (rv) { |
|
469 printf("PROBLEM: subjectPublicKey is not a DER PKCS1 RSAPublicKey.\n"); |
|
470 } else { |
|
471 int mlen; |
|
472 int pubexp; |
|
473 if (verbose) { |
|
474 printf("Decoded RSA Public Key ok. Doing key checks.\n"); |
|
475 } |
|
476 PORT_Assert(rsapubkey->keyType == rsaKey); /* XXX RSA */ |
|
477 mlen = checkInteger(&rsapubkey->u.rsa.modulus, "Modulus", verbose); |
|
478 printf("INFO: Public Key modulus length in bits: %d\n", mlen); |
|
479 if (mlen > MAX_MODULUS) { |
|
480 printf("PROBLEM: Modulus length exceeds %d bits.\n", |
|
481 MAX_MODULUS); |
|
482 } |
|
483 if (mlen < 512) { |
|
484 printf("WARNING: Short modulus.\n"); |
|
485 } |
|
486 if (mlen != (1 << (ffs(mlen)-1))) { |
|
487 printf("WARNING: Unusual modulus length (not a power of two).\n"); |
|
488 } |
|
489 checkInteger(&rsapubkey->u.rsa.publicExponent, "Public Exponent", |
|
490 verbose); |
|
491 pubexp = DER_GetInteger(&rsapubkey->u.rsa.publicExponent); |
|
492 if (pubexp != 17 && pubexp != 3 && pubexp != 65537) { |
|
493 printf("WARNING: Public exponent not any of: 3, 17, 65537\n"); |
|
494 } |
|
495 } |
|
496 |
|
497 |
|
498 /* Name checks */ |
|
499 checkName(&cert->issuer, "Issuer Name", verbose); |
|
500 checkName(&cert->subject, "Subject Name", verbose); |
|
501 |
|
502 if (issuerCert) { |
|
503 SECComparison c = |
|
504 CERT_CompareName(&cert->issuer, &issuerCert->subject); |
|
505 if (c) { |
|
506 printf("PROBLEM: Issuer Name and Subject in Issuing Cert differ\n"); |
|
507 } |
|
508 } |
|
509 |
|
510 /* Check if self-signed */ |
|
511 selfSigned = (CERT_CompareName(&cert->issuer, &cert->subject) == 0); |
|
512 if (selfSigned) { |
|
513 printf("INFO: Certificate is self signed.\n"); |
|
514 } else { |
|
515 printf("INFO: Certificate is NOT self-signed.\n"); |
|
516 } |
|
517 |
|
518 |
|
519 /* Validity time check */ |
|
520 if (CERT_CertTimesValid(cert) == SECSuccess) { |
|
521 printf("INFO: Inside validity period of certificate.\n"); |
|
522 } else { |
|
523 printf("PROBLEM: Not in validity period of certificate.\n"); |
|
524 invalid = 1; |
|
525 } |
|
526 |
|
527 /* Signature check if self-signed */ |
|
528 if (selfSigned && !invalid) { |
|
529 if (rsapubkey->u.rsa.modulus.len) { |
|
530 SECStatus ver; |
|
531 if (verbose) { |
|
532 printf("Checking self signature.\n"); |
|
533 } |
|
534 ver = OurVerifySignedData(signedData, cert); |
|
535 if (ver != SECSuccess) { |
|
536 printf("PROBLEM: Verification of self-signature failed!\n"); |
|
537 } else { |
|
538 printf("INFO: Self-signature verifies ok.\n"); |
|
539 } |
|
540 } else { |
|
541 printf("INFO: Not checking signature due to key problems.\n"); |
|
542 } |
|
543 } else if (!selfSigned && !invalid && issuerCert) { |
|
544 SECStatus ver; |
|
545 ver = OurVerifySignedData(signedData, issuerCert); |
|
546 if (ver != SECSuccess) { |
|
547 printf("PROBLEM: Verification of issuer's signature failed!\n"); |
|
548 } else { |
|
549 printf("INFO: Issuer's signature verifies ok.\n"); |
|
550 } |
|
551 } else { |
|
552 printf("INFO: Not checking signature.\n"); |
|
553 } |
|
554 |
|
555 return 0; |
|
556 } |
|
557 |
|
558 |
|
559 |