From 1dc8d1f93213a08d8f63bcadf31ce0e3d169b936 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Fri, 18 Nov 2011 15:05:34 -0500 Subject: [PATCH] GTlsCertificate: support unencrypted PKCS#8 private keys PKCS#8 is the "right" way to encode private keys. Although the APIs do not currently support encrypted keys, we should at least support unencrypted PKCS#8 keys. https://bugzilla.gnome.org/show_bug.cgi?id=664321 --- gio/gtlscertificate.c | 76 ++++++++++++++++++++++++++----------- gio/tests/key8.pem | 16 ++++++++ gio/tests/tls-certificate.c | 36 +++++++++++++++++- 3 files changed, 105 insertions(+), 23 deletions(-) create mode 100644 gio/tests/key8.pem diff --git a/gio/gtlscertificate.c b/gio/gtlscertificate.c index 56382d5af..2cea2be8b 100644 --- a/gio/gtlscertificate.c +++ b/gio/gtlscertificate.c @@ -134,9 +134,14 @@ g_tls_certificate_class_init (GTlsCertificateClass *class) * GTlsCertificate:private-key: * * The DER (binary) encoded representation of the certificate's - * private key. This property (or the - * #GTlsCertificate:private-key-pem property) can be set when - * constructing a key (eg, from a file), but cannot be read. + * private key, in either PKCS#1 format or unencrypted PKCS#8 + * format. This property (or the #GTlsCertificate:private-key-pem + * property) can be set when constructing a key (eg, from a file), + * but cannot be read. + * + * PKCS#8 format is supported since 2.32; earlier releases only + * support PKCS#1. You can use the openssl rsa + * tool to convert PKCS#8 keys to PKCS#1. * * Since: 2.28 */ @@ -152,9 +157,15 @@ g_tls_certificate_class_init (GTlsCertificateClass *class) * GTlsCertificate:private-key-pem: * * The PEM (ASCII) encoded representation of the certificate's - * private key. This property (or the #GTlsCertificate:private-key - * property) can be set when constructing a key (eg, from a file), - * but cannot be read. + * private key in either PKCS#1 format ("BEGIN RSA PRIVATE + * KEY") or unencrypted PKCS#8 format ("BEGIN + * PRIVATE KEY"). This property (or the + * #GTlsCertificate:private-key property) can be set when + * constructing a key (eg, from a file), but cannot be read. + * + * PKCS#8 format is supported since 2.32; earlier releases only + * support PKCS#1. You can use the openssl rsa + * tool to convert PKCS#8 keys to PKCS#1. * * Since: 2.28 */ @@ -204,10 +215,14 @@ g_tls_certificate_new_internal (const gchar *certificate_pem, return G_TLS_CERTIFICATE (cert); } -#define PEM_CERTIFICATE_HEADER "-----BEGIN CERTIFICATE-----" -#define PEM_CERTIFICATE_FOOTER "-----END CERTIFICATE-----" -#define PEM_PRIVKEY_HEADER "-----BEGIN RSA PRIVATE KEY-----" -#define PEM_PRIVKEY_FOOTER "-----END RSA PRIVATE KEY-----" +#define PEM_CERTIFICATE_HEADER "-----BEGIN CERTIFICATE-----" +#define PEM_CERTIFICATE_FOOTER "-----END CERTIFICATE-----" +#define PEM_PKCS1_PRIVKEY_HEADER "-----BEGIN RSA PRIVATE KEY-----" +#define PEM_PKCS1_PRIVKEY_FOOTER "-----END RSA PRIVATE KEY-----" +#define PEM_PKCS8_PRIVKEY_HEADER "-----BEGIN PRIVATE KEY-----" +#define PEM_PKCS8_PRIVKEY_FOOTER "-----END PRIVATE KEY-----" +#define PEM_PKCS8_ENCRYPTED_HEADER "-----BEGIN ENCRYPTED PRIVATE KEY-----" +#define PEM_PKCS8_ENCRYPTED_FOOTER "-----END ENCRYPTED PRIVATE KEY-----" static gchar * parse_private_key (const gchar *data, @@ -215,27 +230,41 @@ parse_private_key (const gchar *data, gboolean required, GError **error) { - const gchar *start, *end; + const gchar *start, *end, *footer; - start = g_strstr_len (data, data_len, PEM_PRIVKEY_HEADER); - if (!start) + start = g_strstr_len (data, data_len, PEM_PKCS1_PRIVKEY_HEADER); + if (start) + footer = PEM_PKCS1_PRIVKEY_FOOTER; + else { - if (required) + start = g_strstr_len (data, data_len, PEM_PKCS8_PRIVKEY_HEADER); + if (start) + footer = PEM_PKCS8_PRIVKEY_FOOTER; + else { - g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE, - _("No PEM-encoded private key found")); + start = g_strstr_len (data, data_len, PEM_PKCS8_ENCRYPTED_HEADER); + if (start) + { + g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE, + _("Cannot decrypt PEM-encoded private key")); + } + else if (required) + { + g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE, + _("No PEM-encoded private key found")); + } + return NULL; } - return NULL; } - end = g_strstr_len (start, data_len - (data - start), PEM_PRIVKEY_FOOTER); + end = g_strstr_len (start, data_len - (data - start), footer); if (!end) { g_set_error_literal (error, G_TLS_ERROR, G_TLS_ERROR_BAD_CERTIFICATE, _("Could not parse PEM-encoded private key")); return NULL; } - end += strlen (PEM_PRIVKEY_FOOTER); + end += strlen (footer); while (*end == '\r' || *end == '\n') end++; @@ -286,7 +315,9 @@ parse_next_pem_certificate (const gchar **data, * * Creates a new #GTlsCertificate from the PEM-encoded data in @data. * If @data includes both a certificate and a private key, then the - * returned certificate will include the private key data as well. + * returned certificate will include the private key data as well. (See + * the #GTlsCertificate:private-key-pem property for information about + * supported formats.) * * If @data includes multiple certificates, only the first one will be * parsed. @@ -336,7 +367,8 @@ g_tls_certificate_new_from_pem (const gchar *data, * * Creates a #GTlsCertificate from the PEM-encoded data in @file. If * @file cannot be read or parsed, the function will return %NULL and - * set @error. Otherwise, this behaves like g_tls_certificate_new(). + * set @error. Otherwise, this behaves like + * g_tls_certificate_new_from_pem(). * * Return value: the new certificate, or %NULL on error * @@ -367,7 +399,7 @@ g_tls_certificate_new_from_file (const gchar *file, * Creates a #GTlsCertificate from the PEM-encoded data in @cert_file * and @key_file. If either file cannot be read or parsed, the * function will return %NULL and set @error. Otherwise, this behaves - * like g_tls_certificate_new(). + * like g_tls_certificate_new_from_pem(). * * Return value: the new certificate, or %NULL on error * diff --git a/gio/tests/key8.pem b/gio/tests/key8.pem new file mode 100644 index 000000000..e607f16ee --- /dev/null +++ b/gio/tests/key8.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKyWtOX6CneVCwQY +x6COFAyjDnKGKelDvKOLfqyga5SlTKdA+zs/m2beiNk95AY2Zih/cKxzbJOswusK +J23zEWRyC1nJ04n2xBISDBzah/ieW998hlt0hH7XdY49VGZLm0FWRBYWuVIW1DvQ +EOE0rdJl+QBvj5CYtJHUwkVpQ9atAgMBAAECgYBROeyRgAiFF0RD/VWWnseY2vTl +uXboLdUOM4y+uhFnuBRHczHKRLlixMErXRGASwHxdWkWAIzNQ7XI4NKF0KwSD2QI +Hd/NJaS6crz5MXZMo4zm5aVDCJ8om9w2KxE1uDevxbKAwxdpZIUDfFahG5nki3SM +5fXy/7HlEceif+RpEQJBAOXoVLCAkt40CRGkw1AZ1GGYx6wdaY00G/dajOqbpamT +Cjllc+wu3emmt3QY3mNfQgbtyVPX94wP38b7dQapOPcCQQDALQzaC/5BSFtoH4pK +U5kM6LiCyeGeydozsdAYMknmejjLYS+dgjdjms8lZ515iUJXdxwyK2OeNwgkcidF +kEh7AkEA2CxqZUOf1RrsZBCeLVT8I4B6TtWhB8o1eZFE6tvLGvVNKcbBBxTSR/4g +hSNVB+7rsIQpR5LMCoBqkzihQtAe5QJBAL0n2p2I7oNdcDNF0D2mmWAedPavNXex +ISh+3d/jJ+BG7z4oc9CqSlCtITWlDliBZR5obAVptc0WR9pvzf3nrZ8CQGXyM+J1 +B0pYSBJrqHmmbj76xct1aXunqOLJ60frhbseeFGvITem2tEY7SFJXW/hvHcMrN4m +IlGVZRaJKXvzby0= +-----END PRIVATE KEY----- diff --git a/gio/tests/tls-certificate.c b/gio/tests/tls-certificate.c index b6e23fe60..1bc349da9 100644 --- a/gio/tests/tls-certificate.c +++ b/gio/tests/tls-certificate.c @@ -30,6 +30,7 @@ typedef struct { gchar *cert_pems[3]; gchar *key_pem; + gchar *key8_pem; } Reference; static void @@ -199,7 +200,35 @@ from_files (const Reference *ref) g_assert (cert); g_object_unref (cert); } - + + +static void +from_files_pkcs8 (const Reference *ref) +{ + GTlsCertificate *cert; + gchar *parsed_cert_pem = NULL; + const gchar *parsed_key_pem = NULL; + GError *error = NULL; + + cert = g_tls_certificate_new_from_files (SRCDIR "/cert1.pem", + SRCDIR "/key8.pem", + &error); + g_assert_no_error (error); + g_assert (cert); + + g_object_get (cert, + "certificate-pem", &parsed_cert_pem, + NULL); + parsed_key_pem = g_test_tls_connection_get_private_key_pem (cert); + g_assert_cmpstr (parsed_cert_pem, ==, ref->cert_pems[0]); + g_free (parsed_cert_pem); + parsed_cert_pem = NULL; + g_assert_cmpstr (parsed_key_pem, ==, ref->key8_pem); + parsed_key_pem = NULL; + + g_object_unref (cert); +} + static void list_from_file (const Reference *ref) { @@ -258,6 +287,9 @@ main (int argc, g_file_get_contents (SRCDIR "/key.pem", &ref.key_pem, NULL, &error); g_assert_no_error (error); g_assert (ref.key_pem); + g_file_get_contents (SRCDIR "/key8.pem", &ref.key8_pem, NULL, &error); + g_assert_no_error (error); + g_assert (ref.key8_pem); g_test_add_data_func ("/tls-certificate/pem-parser", &ref, (GTestDataFunc)pem_parser); @@ -265,6 +297,8 @@ main (int argc, &ref, (GTestDataFunc)from_file); g_test_add_data_func ("/tls-certificate/from_files", &ref, (GTestDataFunc)from_files); + g_test_add_data_func ("/tls-certificate/from_files_pkcs8", + &ref, (GTestDataFunc)from_files_pkcs8); g_test_add_data_func ("/tls-certificate/list_from_file", &ref, (GTestDataFunc)list_from_file);