Application.TlsDatabase: Add unit tests for local (non-GCR) pinning
Make the class internal so it can be tested, add unit tests covering both in-memory-only and on-disk pinning.
This commit is contained in:
parent
423a55b00f
commit
964b03c068
4 changed files with 225 additions and 2 deletions
|
|
@ -153,8 +153,13 @@ public class Application.CertificateManager : GLib.Object {
|
|||
}
|
||||
|
||||
|
||||
/** TLS database that observes locally pinned certs. */
|
||||
private class Application.TlsDatabase : GLib.TlsDatabase {
|
||||
/**
|
||||
* TLS database that observes locally pinned certs.
|
||||
*
|
||||
* An instance of this is managed by {@link CertificateManager}, the
|
||||
* application should simply construct an instance of that.
|
||||
*/
|
||||
internal class Application.TlsDatabase : GLib.TlsDatabase {
|
||||
|
||||
|
||||
/** A certificate and the identities it is trusted for. */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* Copyright © 2020 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
|
||||
class Application.CertificateManagerTest : TestCase {
|
||||
|
||||
|
||||
private const string IDENITY_HOSTNAME = "localhost";
|
||||
private const uint16 IDENITY_PORT = 143;
|
||||
|
||||
private static int cert_id = 1;
|
||||
|
||||
private GLib.File? tmp = null;
|
||||
private GLib.File? db_dir = null;
|
||||
private GLib.File? cert_dir = null;
|
||||
|
||||
|
||||
public CertificateManagerTest() {
|
||||
base("Application.CertificateManagerTest");
|
||||
add_test(
|
||||
"database_memory_certificate_pinning_without_gcr",
|
||||
database_memory_certificate_pinning_without_gcr
|
||||
);
|
||||
add_test(
|
||||
"database_disk_certificate_pinning_without_gcr",
|
||||
database_disk_certificate_pinning_without_gcr
|
||||
);
|
||||
}
|
||||
|
||||
public override void set_up() throws GLib.Error {
|
||||
this.tmp = GLib.File.new_for_path(
|
||||
GLib.DirUtils.make_tmp("application-certificate-manager-test-XXXXXX")
|
||||
);
|
||||
this.db_dir = this.tmp.get_child("db");
|
||||
this.db_dir.make_directory();
|
||||
this.cert_dir = this.tmp.get_child("certs");
|
||||
this.cert_dir.make_directory();
|
||||
}
|
||||
|
||||
public override void tear_down() throws GLib.Error {
|
||||
delete_file(this.tmp);
|
||||
this.db_dir = null;
|
||||
this.cert_dir = null;
|
||||
this.tmp = null;
|
||||
}
|
||||
|
||||
public void database_memory_certificate_pinning_without_gcr()
|
||||
throws GLib.Error {
|
||||
var test_article1 = new Application.TlsDatabase(
|
||||
GLib.TlsBackend.get_default().get_default_database(),
|
||||
this.db_dir,
|
||||
false
|
||||
);
|
||||
var id = new_identity();
|
||||
var cert1 = new_cert("cert1");
|
||||
var cert2 = new_cert("cert2");
|
||||
|
||||
// Assert the db doesn't know about the cert first up.
|
||||
assert_pinning(test_article1, cert1, id, false);
|
||||
assert_pinning(test_article1, cert2, id, false);
|
||||
|
||||
// Pin a cert in the db
|
||||
test_article1.pin_certificate.begin(
|
||||
cert1, id, false, null, this.async_completion
|
||||
);
|
||||
test_article1.pin_certificate.end(this.async_result());
|
||||
|
||||
// Assert the db now knows about it, but not the other
|
||||
assert_pinning(test_article1, cert1, id, true);
|
||||
assert_pinning(test_article1, cert2, id, false);
|
||||
|
||||
// Construct a new test article and ensure it doesn't know
|
||||
// about either
|
||||
var test_article2 = new Application.TlsDatabase(
|
||||
GLib.TlsBackend.get_default().get_default_database(),
|
||||
this.db_dir,
|
||||
false
|
||||
);
|
||||
assert_pinning(test_article2, cert1, id, false);
|
||||
assert_pinning(test_article2, cert2, id, false);
|
||||
}
|
||||
|
||||
public void database_disk_certificate_pinning_without_gcr()
|
||||
throws GLib.Error {
|
||||
var test_article1 = new Application.TlsDatabase(
|
||||
GLib.TlsBackend.get_default().get_default_database(),
|
||||
this.db_dir,
|
||||
false
|
||||
);
|
||||
var id = new_identity();
|
||||
var cert1 = new_cert("cert1");
|
||||
var cert2 = new_cert("cert2");
|
||||
|
||||
// Assert the db doesn't know about the cert first up.
|
||||
assert_pinning(test_article1, cert1, id, false);
|
||||
assert_pinning(test_article1, cert2, id, false);
|
||||
|
||||
// Pin a cert in the db
|
||||
test_article1.pin_certificate.begin(
|
||||
cert1, id, true, null, this.async_completion
|
||||
);
|
||||
test_article1.pin_certificate.end(this.async_result());
|
||||
|
||||
// Assert the db now knows about it, but not the other
|
||||
assert_pinning(test_article1, cert1, id, true);
|
||||
assert_pinning(test_article1, cert2, id, false);
|
||||
|
||||
// Construct a new test article and ensure it has loaded the
|
||||
// first from disk
|
||||
var test_article2 = new Application.TlsDatabase(
|
||||
GLib.TlsBackend.get_default().get_default_database(),
|
||||
this.db_dir,
|
||||
false
|
||||
);
|
||||
assert_pinning(test_article2, cert1, id, true);
|
||||
assert_pinning(test_article2, cert2, id, false);
|
||||
}
|
||||
|
||||
private void assert_pinning(Application.TlsDatabase db,
|
||||
GLib.TlsCertificate cert,
|
||||
GLib.SocketConnectable id,
|
||||
bool is_pinned)
|
||||
throws GLib.Error {
|
||||
// Test both the sync and async calls to ensure equivalence
|
||||
var sync_ret = db.verify_chain(
|
||||
cert,
|
||||
GLib.TlsDatabase.PURPOSE_AUTHENTICATE_SERVER,
|
||||
id,
|
||||
null,
|
||||
NONE,
|
||||
null
|
||||
);
|
||||
if (is_pinned) {
|
||||
assert_true(
|
||||
sync_ret == 0,
|
||||
"is pinned sync"
|
||||
);
|
||||
} else {
|
||||
assert_true(
|
||||
sync_ret == GLib.TlsCertificateFlags.UNKNOWN_CA,
|
||||
"not pinned sync"
|
||||
);
|
||||
}
|
||||
|
||||
db.verify_chain_async.begin(
|
||||
cert,
|
||||
GLib.TlsDatabase.PURPOSE_AUTHENTICATE_SERVER,
|
||||
id,
|
||||
null,
|
||||
NONE,
|
||||
null,
|
||||
this.async_completion
|
||||
);
|
||||
var async_ret = db.verify_chain_async.end(this.async_result());
|
||||
if (is_pinned) {
|
||||
assert_true(
|
||||
async_ret == 0,
|
||||
"is pinned async"
|
||||
);
|
||||
} else {
|
||||
assert_true(
|
||||
async_ret == GLib.TlsCertificateFlags.UNKNOWN_CA,
|
||||
"not pinned async"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private GLib.SocketConnectable new_identity() {
|
||||
return new GLib.NetworkAddress(IDENITY_HOSTNAME, IDENITY_PORT);
|
||||
}
|
||||
|
||||
private GLib.TlsCertificate new_cert(string name) throws GLib.Error {
|
||||
var priv_name = name + ".priv";
|
||||
var cert_name = name + ".cert";
|
||||
var template_name = name + ".template";
|
||||
GLib.Process.spawn_sync(
|
||||
this.cert_dir.get_path(),
|
||||
{
|
||||
"certtool", "--generate-privkey", "--outfile", priv_name
|
||||
},
|
||||
GLib.Environ.get(),
|
||||
SpawnFlags.SEARCH_PATH,
|
||||
null
|
||||
);
|
||||
this.cert_dir.get_child(template_name).create(NONE).write("""
|
||||
organization = "Example Inc."
|
||||
country = AU
|
||||
serial = %d
|
||||
expiration_days = 1
|
||||
dns_name = "%s"
|
||||
encryption_key
|
||||
""".printf(CertificateManagerTest.cert_id++, IDENITY_HOSTNAME).data);
|
||||
|
||||
GLib.Process.spawn_sync(
|
||||
this.cert_dir.get_path(),
|
||||
{
|
||||
"certtool",
|
||||
"--generate-self-signed",
|
||||
"--load-privkey", priv_name,
|
||||
"--template", template_name,
|
||||
"--outfile", cert_name
|
||||
},
|
||||
GLib.Environ.get(),
|
||||
SpawnFlags.SEARCH_PATH,
|
||||
null
|
||||
);
|
||||
return new GLib.TlsCertificate.from_file(
|
||||
this.cert_dir.get_child(cert_name).get_path()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -79,6 +79,7 @@ test_client_sources = [
|
|||
'test-client.vala',
|
||||
|
||||
'client/accounts/accounts-manager-test.vala',
|
||||
'client/application/application-certificate-manager-test.vala',
|
||||
'client/application/application-client-test.vala',
|
||||
'client/application/application-configuration-test.vala',
|
||||
'client/components/client-web-view-test.vala',
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ int main(string[] args) {
|
|||
// Keep this before other ClientWebView based tests since it tests
|
||||
// WebContext init
|
||||
client.add_suite(new Accounts.ManagerTest().suite);
|
||||
client.add_suite(new Application.CertificateManagerTest().suite);
|
||||
client.add_suite(new Application.ClientTest().suite);
|
||||
client.add_suite(new Application.ConfigurationTest().suite);
|
||||
client.add_suite(new ClientWebViewTest().suite);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue