Mercurial > pidgin
comparison libpurple/certificate.c @ 29298:fb99a0067812
propagate from branch 'im.pidgin.pidgin' (head 70d69397ed952b26b453423c381c70d6783eb66d)
to branch 'im.pidgin.cpw.attention_ui' (head 1cf0dea282a0d0e4aeac4770e0150d6d0c10830a)
author | Marcus Lundblad <ml@update.uu.se> |
---|---|
date | Thu, 13 Aug 2009 17:42:44 +0000 |
parents | fcf2d3b9f281 |
children | 35f3a79045a6 |
comparison
equal
deleted
inserted
replaced
29297:338d6a211055 | 29298:fb99a0067812 |
---|---|
384 g_return_val_if_fail(crt->scheme, FALSE); | 384 g_return_val_if_fail(crt->scheme, FALSE); |
385 g_return_val_if_fail(name, FALSE); | 385 g_return_val_if_fail(name, FALSE); |
386 | 386 |
387 scheme = crt->scheme; | 387 scheme = crt->scheme; |
388 | 388 |
389 /* TODO: Instead of failing, maybe use get_subject_name and strcmp? */ | |
390 g_return_val_if_fail(scheme->check_subject_name, FALSE); | 389 g_return_val_if_fail(scheme->check_subject_name, FALSE); |
391 | 390 |
392 return (scheme->check_subject_name)(crt, name); | 391 return (scheme->check_subject_name)(crt, name); |
393 } | 392 } |
394 | 393 |
1316 purple_certificate_destroy(cached_crt); | 1315 purple_certificate_destroy(cached_crt); |
1317 g_byte_array_free(peer_fpr, TRUE); | 1316 g_byte_array_free(peer_fpr, TRUE); |
1318 g_byte_array_free(cached_fpr, TRUE); | 1317 g_byte_array_free(cached_fpr, TRUE); |
1319 } | 1318 } |
1320 | 1319 |
1320 /* | |
1321 * This is called from two points in x509_tls_cached_unknown_peer below | |
1322 * once we've verified the signature chain is valid. Now we need to verify | |
1323 * the subject name of the certificate. | |
1324 */ | |
1325 static void | |
1326 x509_tls_cached_check_subject_name(PurpleCertificateVerificationRequest *vrq, | |
1327 gboolean had_ca_pool) | |
1328 { | |
1329 PurpleCertificatePool *tls_peers; | |
1330 PurpleCertificate *peer_crt; | |
1331 GList *chain = vrq->cert_chain; | |
1332 | |
1333 peer_crt = (PurpleCertificate *) chain->data; | |
1334 | |
1335 /* Last, check that the hostname matches */ | |
1336 if ( ! purple_certificate_check_subject_name(peer_crt, | |
1337 vrq->subject_name) ) { | |
1338 gchar *sn = purple_certificate_get_subject_name(peer_crt); | |
1339 | |
1340 purple_debug_error("certificate/x509/tls_cached", | |
1341 "Name mismatch: Certificate given for %s " | |
1342 "has a name of %s\n", | |
1343 vrq->subject_name, sn); | |
1344 | |
1345 if (had_ca_pool) { | |
1346 /* Prompt the user to authenticate the certificate */ | |
1347 /* TODO: Provide the user with more guidance about why he is | |
1348 being prompted */ | |
1349 /* vrq will be completed by user_auth */ | |
1350 gchar *msg; | |
1351 msg = g_strdup_printf(_("The certificate presented by \"%s\" " | |
1352 "claims to be from \"%s\" instead. " | |
1353 "This could mean that you are not " | |
1354 "connecting to the service you " | |
1355 "believe you are."), | |
1356 vrq->subject_name, sn); | |
1357 | |
1358 x509_tls_cached_user_auth(vrq, msg); | |
1359 g_free(msg); | |
1360 } else { | |
1361 /* Had no CA pool, so couldn't verify the chain *and* | |
1362 * the subject name isn't valid. | |
1363 * I think this is bad enough to warrant a fatal error. It's | |
1364 * not likely anyway... | |
1365 */ | |
1366 purple_notify_error(NULL, /* TODO: Probably wrong. */ | |
1367 _("SSL Certificate Error"), | |
1368 _("Invalid certificate chain"), | |
1369 _("You have no database of root certificates, so " | |
1370 "this certificate cannot be validated.")); | |
1371 } | |
1372 | |
1373 g_free(sn); | |
1374 return; | |
1375 } /* if (name mismatch) */ | |
1376 | |
1377 if (!had_ca_pool) { | |
1378 /* The subject name is correct, but we weren't able to verify the | |
1379 * chain because there was no pool of root CAs found. Prompt the user | |
1380 * to validate it. | |
1381 */ | |
1382 | |
1383 /* vrq will be completed by user_auth */ | |
1384 x509_tls_cached_user_auth(vrq,_("You have no database of root " | |
1385 "certificates, so this " | |
1386 "certificate cannot be " | |
1387 "validated.")); | |
1388 return; | |
1389 } | |
1390 | |
1391 /* If we reach this point, the certificate is good. */ | |
1392 /* Look up the local cache and store it there for future use */ | |
1393 tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name, | |
1394 "tls_peers"); | |
1395 | |
1396 if (tls_peers) { | |
1397 if (!purple_certificate_pool_store(tls_peers,vrq->subject_name, | |
1398 peer_crt) ) { | |
1399 purple_debug_error("certificate/x509/tls_cached", | |
1400 "FAILED to cache peer certificate\n"); | |
1401 } | |
1402 } else { | |
1403 purple_debug_error("certificate/x509/tls_cached", | |
1404 "Unable to locate tls_peers certificate " | |
1405 "cache.\n"); | |
1406 } | |
1407 | |
1408 /* Whew! Done! */ | |
1409 purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID); | |
1410 | |
1411 } | |
1412 | |
1321 /* For when we've never communicated with this party before */ | 1413 /* For when we've never communicated with this party before */ |
1322 /* TODO: Need ways to specify possibly multiple problems with a cert, or at | 1414 /* TODO: Need ways to specify possibly multiple problems with a cert, or at |
1323 least reprioritize them. For example, maybe the signature ought to be | 1415 least reprioritize them. |
1324 checked BEFORE the hostname checking? | 1416 */ |
1325 Stu thinks we should check the signature before the name, so we do now. | |
1326 The above TODO still stands. */ | |
1327 static void | 1417 static void |
1328 x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq) | 1418 x509_tls_cached_unknown_peer(PurpleCertificateVerificationRequest *vrq) |
1329 { | 1419 { |
1330 PurpleCertificatePool *ca, *tls_peers; | 1420 PurpleCertificatePool *ca; |
1331 PurpleCertificate *peer_crt; | 1421 PurpleCertificate *peer_crt; |
1422 PurpleCertificate *ca_crt, *end_crt; | |
1332 PurpleCertificate *failing_crt; | 1423 PurpleCertificate *failing_crt; |
1333 GList *chain = vrq->cert_chain; | 1424 GList *chain = vrq->cert_chain; |
1334 gboolean chain_validated = FALSE; | 1425 GByteArray *last_fpr, *ca_fpr; |
1426 gchar *ca_id; | |
1335 | 1427 |
1336 peer_crt = (PurpleCertificate *) chain->data; | 1428 peer_crt = (PurpleCertificate *) chain->data; |
1337 | 1429 |
1338 /* TODO: Figure out a way to check for a bad signature, as opposed to | 1430 /* TODO: Figure out a way to check for a bad signature, as opposed to |
1339 "not self-signed" */ | 1431 "not self-signed" */ |
1359 | 1451 |
1360 /* Next, attempt to verify the last certificate against a CA */ | 1452 /* Next, attempt to verify the last certificate against a CA */ |
1361 ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca"); | 1453 ca = purple_certificate_find_pool(x509_tls_cached.scheme_name, "ca"); |
1362 | 1454 |
1363 /* Next, check that the certificate chain is valid */ | 1455 /* Next, check that the certificate chain is valid */ |
1364 if (purple_certificate_check_signature_chain_with_failing(chain, | 1456 if (!purple_certificate_check_signature_chain_with_failing(chain, |
1365 &failing_crt)) | 1457 &failing_crt)) |
1366 chain_validated = TRUE; | 1458 { |
1367 else { | 1459 gboolean chain_validated = FALSE; |
1368 /* | 1460 /* |
1369 * Check if the failing certificate is in the CA store. If it is, then | 1461 * Check if the failing certificate is in the CA store. If it is, then |
1370 * consider this fully validated. This works around issues with some | 1462 * consider this fully validated. This works around issues with some |
1371 * prominent intermediate CAs whose signature is md5WithRSAEncryption. | 1463 * prominent intermediate CAs whose signature is md5WithRSAEncryption. |
1372 * I'm looking at CACert Class 3 here. See #4458 for details. | 1464 * I'm looking at CACert Class 3 here. See #4458 for details. |
1397 | 1489 |
1398 /* | 1490 /* |
1399 * If we get here, either the cert matched the stuff right above | 1491 * If we get here, either the cert matched the stuff right above |
1400 * or it didn't, in which case we give up and complain to the user. | 1492 * or it didn't, in which case we give up and complain to the user. |
1401 */ | 1493 */ |
1402 if (!chain_validated) { | 1494 if (chain_validated) { |
1495 x509_tls_cached_check_subject_name(vrq, TRUE); | |
1496 } else { | |
1403 /* TODO: Tell the user where the chain broke? */ | 1497 /* TODO: Tell the user where the chain broke? */ |
1404 /* TODO: This error will hopelessly confuse any | 1498 /* TODO: This error will hopelessly confuse any |
1405 non-elite user. */ | 1499 non-elite user. */ |
1406 gchar *secondary; | 1500 gchar *secondary; |
1407 | 1501 |
1419 g_free(secondary); | 1513 g_free(secondary); |
1420 | 1514 |
1421 /* Okay, we're done here */ | 1515 /* Okay, we're done here */ |
1422 purple_certificate_verify_complete(vrq, | 1516 purple_certificate_verify_complete(vrq, |
1423 PURPLE_CERTIFICATE_INVALID); | 1517 PURPLE_CERTIFICATE_INVALID); |
1424 return; | |
1425 } | 1518 } |
1519 | |
1520 return; | |
1426 } /* if (signature chain not good) */ | 1521 } /* if (signature chain not good) */ |
1427 | 1522 |
1428 /* If, for whatever reason, there is no Certificate Authority pool | 1523 /* If, for whatever reason, there is no Certificate Authority pool |
1429 loaded, we will simply present it to the user for checking. */ | 1524 loaded, we'll verify the subject name and then warn about thsi. */ |
1430 if ( !ca ) { | 1525 if ( !ca ) { |
1431 purple_debug_error("certificate/x509/tls_cached", | 1526 purple_debug_error("certificate/x509/tls_cached", |
1432 "No X.509 Certificate Authority pool " | 1527 "No X.509 Certificate Authority pool " |
1433 "could be found!\n"); | 1528 "could be found!\n"); |
1434 | 1529 |
1530 x509_tls_cached_check_subject_name(vrq, FALSE); | |
1531 return; | |
1532 } | |
1533 | |
1534 end_crt = g_list_last(chain)->data; | |
1535 | |
1536 /* Attempt to look up the last certificate's issuer */ | |
1537 ca_id = purple_certificate_get_issuer_unique_id(end_crt); | |
1538 purple_debug_info("certificate/x509/tls_cached", | |
1539 "Checking for a CA with DN=%s\n", | |
1540 ca_id); | |
1541 ca_crt = purple_certificate_pool_retrieve(ca, ca_id); | |
1542 if ( NULL == ca_crt ) { | |
1543 purple_debug_warning("certificate/x509/tls_cached", | |
1544 "Certificate Authority with DN='%s' not " | |
1545 "found. I'll prompt the user, I guess.\n", | |
1546 ca_id); | |
1547 g_free(ca_id); | |
1435 /* vrq will be completed by user_auth */ | 1548 /* vrq will be completed by user_auth */ |
1436 x509_tls_cached_user_auth(vrq,_("You have no database of root " | 1549 x509_tls_cached_user_auth(vrq,_("The root certificate this " |
1437 "certificates, so this " | 1550 "one claims to be issued by " |
1438 "certificate cannot be " | 1551 "is unknown to Pidgin.")); |
1439 "validated.")); | |
1440 return; | 1552 return; |
1441 } | 1553 } |
1442 | 1554 |
1443 if (!chain_validated) { | 1555 g_free(ca_id); |
1444 GByteArray *last_fpr, *ca_fpr; | 1556 |
1445 PurpleCertificate *ca_crt, *end_crt; | 1557 /* |
1446 gchar *ca_id; | 1558 * Check the fingerprints; if they match, then this certificate *is* one |
1447 | 1559 * of the designated "trusted roots", and we don't need to verify the |
1448 end_crt = g_list_last(chain)->data; | 1560 * signature. This is good because some of the older roots are self-signed |
1449 | 1561 * with bad hash algorithms that we don't want to allow in any other |
1450 /* Attempt to look up the last certificate's issuer */ | 1562 * circumstances (one of Verisign's root CAs is self-signed with MD2). |
1451 ca_id = purple_certificate_get_issuer_unique_id(end_crt); | 1563 * |
1452 purple_debug_info("certificate/x509/tls_cached", | 1564 * If the fingerprints don't match, we'll fall back to checking the |
1453 "Checking for a CA with DN=%s\n", | 1565 * signature. |
1454 ca_id); | 1566 * |
1455 ca_crt = purple_certificate_pool_retrieve(ca, ca_id); | 1567 * GnuTLS doesn't seem to include the final root in the verification |
1456 if ( NULL == ca_crt ) { | 1568 * list, so this check will never succeed. NSS *does* include it in |
1457 purple_debug_warning("certificate/x509/tls_cached", | 1569 * the list, so here we are. |
1458 "Certificate Authority with DN='%s' not " | 1570 */ |
1459 "found. I'll prompt the user, I guess.\n", | 1571 last_fpr = purple_certificate_get_fingerprint_sha1(end_crt); |
1460 ca_id); | 1572 ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt); |
1461 g_free(ca_id); | 1573 |
1462 /* vrq will be completed by user_auth */ | 1574 if ( !byte_arrays_equal(last_fpr, ca_fpr) && |
1463 x509_tls_cached_user_auth(vrq,_("The root certificate this " | 1575 !purple_certificate_signed_by(end_crt, ca_crt) ) |
1464 "one claims to be issued by " | 1576 { |
1465 "is unknown to Pidgin.")); | 1577 /* TODO: If signed_by ever returns a reason, maybe mention |
1466 return; | 1578 that, too. */ |
1467 } | 1579 /* TODO: Also mention the CA involved. While I could do this |
1468 | 1580 now, a full DN is a little much with which to assault the |
1469 g_free(ca_id); | 1581 user's poor, leaky eyes. */ |
1470 | 1582 /* TODO: This error message makes my eyes cross, and I wrote it */ |
1471 /* | 1583 gchar * secondary = |
1472 * Check the fingerprints; if they match, then this certificate *is* one | 1584 g_strdup_printf(_("The certificate chain presented by " |
1473 * of the designated "trusted roots", and we don't need to verify the | 1585 "%s does not have a valid digital " |
1474 * signature. This is good because some of the older roots are self-signed | 1586 "signature from the Certificate " |
1475 * with bad hash algorithms that we don't want to allow in any other | 1587 "Authority from which it claims to " |
1476 * circumstances (one of Verisign's root CAs is self-signed with MD2). | 1588 "have a signature."), |
1477 * | 1589 vrq->subject_name); |
1478 * If the fingerprints don't match, we'll fall back to checking the | 1590 |
1479 * signature. | 1591 purple_notify_error(NULL, /* TODO: Probably wrong */ |
1480 * | 1592 _("SSL Certificate Error"), |
1481 * GnuTLS doesn't seem to include the final root in the verification | 1593 _("Invalid certificate authority" |
1482 * list, so this check will never succeed. NSS *does* include it in | 1594 " signature"), |
1483 * the list, so here we are. | 1595 secondary); |
1484 */ | 1596 g_free(secondary); |
1485 last_fpr = purple_certificate_get_fingerprint_sha1(end_crt); | 1597 |
1486 ca_fpr = purple_certificate_get_fingerprint_sha1(ca_crt); | 1598 /* Signal "bad cert" */ |
1487 | 1599 purple_certificate_verify_complete(vrq, |
1488 if ( !byte_arrays_equal(last_fpr, ca_fpr) && | 1600 PURPLE_CERTIFICATE_INVALID); |
1489 !purple_certificate_signed_by(end_crt, ca_crt) ) | 1601 |
1490 { | 1602 purple_certificate_destroy(ca_crt); |
1491 /* TODO: If signed_by ever returns a reason, maybe mention | |
1492 that, too. */ | |
1493 /* TODO: Also mention the CA involved. While I could do this | |
1494 now, a full DN is a little much with which to assault the | |
1495 user's poor, leaky eyes. */ | |
1496 /* TODO: This error message makes my eyes cross, and I wrote it */ | |
1497 gchar * secondary = | |
1498 g_strdup_printf(_("The certificate chain presented by " | |
1499 "%s does not have a valid digital " | |
1500 "signature from the Certificate " | |
1501 "Authority from which it claims to " | |
1502 "have a signature."), | |
1503 vrq->subject_name); | |
1504 | |
1505 purple_notify_error(NULL, /* TODO: Probably wrong */ | |
1506 _("SSL Certificate Error"), | |
1507 _("Invalid certificate authority" | |
1508 " signature"), | |
1509 secondary); | |
1510 g_free(secondary); | |
1511 | |
1512 /* Signal "bad cert" */ | |
1513 purple_certificate_verify_complete(vrq, | |
1514 PURPLE_CERTIFICATE_INVALID); | |
1515 | |
1516 purple_certificate_destroy(ca_crt); | |
1517 g_byte_array_free(ca_fpr, TRUE); | |
1518 g_byte_array_free(last_fpr, TRUE); | |
1519 return; | |
1520 } /* if (CA signature not good) */ | |
1521 | |
1522 g_byte_array_free(ca_fpr, TRUE); | 1603 g_byte_array_free(ca_fpr, TRUE); |
1523 g_byte_array_free(last_fpr, TRUE); | 1604 g_byte_array_free(last_fpr, TRUE); |
1524 } | |
1525 | |
1526 /* Last, check that the hostname matches */ | |
1527 if ( ! purple_certificate_check_subject_name(peer_crt, | |
1528 vrq->subject_name) ) { | |
1529 gchar *sn = purple_certificate_get_subject_name(peer_crt); | |
1530 gchar *msg; | |
1531 | |
1532 purple_debug_error("certificate/x509/tls_cached", | |
1533 "Name mismatch: Certificate given for %s " | |
1534 "has a name of %s\n", | |
1535 vrq->subject_name, sn); | |
1536 | |
1537 /* Prompt the user to authenticate the certificate */ | |
1538 /* TODO: Provide the user with more guidance about why he is | |
1539 being prompted */ | |
1540 /* vrq will be completed by user_auth */ | |
1541 msg = g_strdup_printf(_("The certificate presented by \"%s\" " | |
1542 "claims to be from \"%s\" instead. " | |
1543 "This could mean that you are not " | |
1544 "connecting to the service you " | |
1545 "believe you are."), | |
1546 vrq->subject_name, sn); | |
1547 | |
1548 x509_tls_cached_user_auth(vrq,msg); | |
1549 | |
1550 g_free(sn); | |
1551 g_free(msg); | |
1552 return; | 1605 return; |
1553 } /* if (name mismatch) */ | 1606 } /* if (CA signature not good) */ |
1554 | 1607 |
1555 /* If we reach this point, the certificate is good. */ | 1608 g_byte_array_free(ca_fpr, TRUE); |
1556 /* Look up the local cache and store it there for future use */ | 1609 g_byte_array_free(last_fpr, TRUE); |
1557 tls_peers = purple_certificate_find_pool(x509_tls_cached.scheme_name, | 1610 |
1558 "tls_peers"); | 1611 x509_tls_cached_check_subject_name(vrq, TRUE); |
1559 | |
1560 if (tls_peers) { | |
1561 if (!purple_certificate_pool_store(tls_peers,vrq->subject_name, | |
1562 peer_crt) ) { | |
1563 purple_debug_error("certificate/x509/tls_cached", | |
1564 "FAILED to cache peer certificate\n"); | |
1565 } | |
1566 } else { | |
1567 purple_debug_error("certificate/x509/tls_cached", | |
1568 "Unable to locate tls_peers certificate " | |
1569 "cache.\n"); | |
1570 } | |
1571 | |
1572 /* Whew! Done! */ | |
1573 purple_certificate_verify_complete(vrq, PURPLE_CERTIFICATE_VALID); | |
1574 } | 1612 } |
1575 | 1613 |
1576 static void | 1614 static void |
1577 x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq) | 1615 x509_tls_cached_start_verify(PurpleCertificateVerificationRequest *vrq) |
1578 { | 1616 { |