libnm-util: recognize PKCS#8 private keys and check passwords (bgo #649326)

Neither gnutls nor NSS fully support PKCS#8 so we don't have complete
support here, but at least recognize the keys and make an attempt to
check the private key if we can.
This commit is contained in:
Dan Williams 2011-05-12 10:09:18 -05:00
parent 5737573314
commit 5deb993ff6
10 changed files with 264 additions and 5 deletions

View file

@ -52,6 +52,12 @@ _nm_crypto_error_quark (void)
#define PEM_CERT_BEGIN "-----BEGIN CERTIFICATE-----"
#define PEM_CERT_END "-----END CERTIFICATE-----"
#define PEM_PKCS8_ENC_KEY_BEGIN "-----BEGIN ENCRYPTED PRIVATE KEY-----"
#define PEM_PKCS8_ENC_KEY_END "-----END ENCRYPTED PRIVATE KEY-----"
#define PEM_PKCS8_DEC_KEY_BEGIN "-----BEGIN PRIVATE KEY-----"
#define PEM_PKCS8_DEC_KEY_END "-----END PRIVATE KEY-----"
static gboolean
find_tag (const char *tag,
const GByteArray *array,
@ -250,6 +256,71 @@ parse_error:
return NULL;
}
static GByteArray *
parse_pkcs8_key_file (const GByteArray *contents,
gboolean *out_encrypted,
GError **error)
{
GByteArray *key = NULL;
gsize start = 0, end = 0;
unsigned char *der = NULL;
guint8 save_end;
gsize length = 0;
const char *start_tag = NULL, *end_tag = NULL;
gboolean encrypted = FALSE;
/* Try encrypted first, decrypted next */
if (find_tag (PEM_PKCS8_ENC_KEY_BEGIN, contents, 0, &start)) {
start_tag = PEM_PKCS8_ENC_KEY_BEGIN;
end_tag = PEM_PKCS8_ENC_KEY_END;
encrypted = TRUE;
} else if (find_tag (PEM_PKCS8_DEC_KEY_BEGIN, contents, 0, &start)) {
start_tag = PEM_PKCS8_DEC_KEY_BEGIN;
end_tag = PEM_PKCS8_DEC_KEY_END;
encrypted = FALSE;
} else {
g_set_error_literal (error, NM_CRYPTO_ERROR,
NM_CRYPTO_ERR_FILE_FORMAT_INVALID,
_("Failed to find expected PKCS#8 start tag."));
return NULL;
}
start += strlen (start_tag);
if (!find_tag (end_tag, contents, start, &end)) {
g_set_error (error, NM_CRYPTO_ERROR,
NM_CRYPTO_ERR_FILE_FORMAT_INVALID,
_("Failed to find expected PKCS#8 end tag '%s'."),
end_tag);
return NULL;
}
/* g_base64_decode() wants a NULL-terminated string */
save_end = contents->data[end];
contents->data[end] = '\0';
der = g_base64_decode ((const char *) (contents->data + start), &length);
contents->data[end] = save_end;
if (der && length) {
key = g_byte_array_sized_new (length);
if (key) {
g_byte_array_append (key, der, length);
g_assert (key->len == length);
*out_encrypted = encrypted;
} else {
g_set_error_literal (error, NM_CRYPTO_ERROR,
NM_CRYPTO_ERR_OUT_OF_MEMORY,
_("Not enough memory to store private key data."));
}
} else {
g_set_error_literal (error, NM_CRYPTO_ERROR,
NM_CRYPTO_ERR_DECODE_FAILED,
_("Failed to decode PKCS#8 private key."));
}
g_free (der);
return key;
}
static GByteArray *
file_to_g_byte_array (const char *filename, GError **error)
{
@ -654,6 +725,7 @@ crypto_verify_private_key_data (const GByteArray *contents,
GByteArray *tmp;
NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN;
NMCryptoKeyType ktype = NM_CRYPTO_KEY_TYPE_UNKNOWN;
gboolean is_encrypted = FALSE;
g_return_val_if_fail (contents != NULL, FALSE);
@ -662,15 +734,29 @@ crypto_verify_private_key_data (const GByteArray *contents,
if (!password || crypto_verify_pkcs12 (contents, password, error))
format = NM_CRYPTO_FILE_FORMAT_PKCS12;
} else {
tmp = crypto_decrypt_private_key_data (contents, password, &ktype, error);
/* Maybe it's PKCS#8 */
tmp = parse_pkcs8_key_file (contents, &is_encrypted, error);
if (tmp) {
if (crypto_verify_pkcs8 (tmp, is_encrypted, password, error))
format = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
} else {
g_clear_error (error);
/* Or it's old-style OpenSSL */
tmp = crypto_decrypt_private_key_data (contents, password, &ktype, error);
if (tmp)
format = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
else if (!password && (ktype != NM_CRYPTO_KEY_TYPE_UNKNOWN))
format = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
}
if (tmp) {
/* Don't leave decrypted key data around */
memset (tmp->data, 0, tmp->len);
g_byte_array_free (tmp, TRUE);
format = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
} else if (!password && (ktype != NM_CRYPTO_KEY_TYPE_UNKNOWN))
format = NM_CRYPTO_FILE_FORMAT_RAW_KEY;
}
}
return format;
}

View file

@ -136,4 +136,9 @@ gboolean crypto_verify_pkcs12 (const GByteArray *data,
const char *password,
GError **error);
gboolean crypto_verify_pkcs8 (const GByteArray *data,
gboolean is_encrypted,
const char *password,
GError **error);
#endif /* __CRYPTO_H__ */

View file

@ -438,6 +438,57 @@ out:
return success;
}
gboolean
crypto_verify_pkcs8 (const GByteArray *data,
gboolean is_encrypted,
const char *password,
GError **error)
{
gnutls_x509_privkey_t p8;
gnutls_datum dt;
int err;
g_return_val_if_fail (data != NULL, FALSE);
dt.data = (unsigned char *) data->data;
dt.size = data->len;
err = gnutls_x509_privkey_init (&p8);
if (err < 0) {
g_set_error (error, NM_CRYPTO_ERROR,
NM_CRYPTO_ERR_DECODE_FAILED,
_("Couldn't initialize PKCS#8 decoder: %s"),
gnutls_strerror (err));
return FALSE;
}
err = gnutls_x509_privkey_import_pkcs8 (p8,
&dt,
GNUTLS_X509_FMT_DER,
is_encrypted ? password : NULL,
is_encrypted ? 0 : GNUTLS_PKCS_PLAIN);
gnutls_x509_privkey_deinit (p8);
if (err < 0) {
if (err == GNUTLS_E_UNKNOWN_CIPHER_TYPE) {
/* HACK: gnutls doesn't support all the cipher types that openssl
* can use with PKCS#8, so if we encounter one, we have to assume
* the given password works. gnutls needs to unsuckify, apparently.
* Specifically, by default openssl uses pbeWithMD5AndDES-CBC
* which gnutls does not support.
*/
} else {
g_set_error (error, NM_CRYPTO_ERROR,
NM_CRYPTO_ERR_FILE_FORMAT_INVALID,
_("Couldn't decode PKCS#8 file: %s"),
gnutls_strerror (err));
return FALSE;
}
}
return TRUE;
}
gboolean
crypto_randomize (void *buffer, gsize buffer_len, GError **error)
{

View file

@ -541,6 +541,21 @@ error:
return FALSE;
}
gboolean
crypto_verify_pkcs8 (const GByteArray *data,
gboolean is_encrypted,
const char *password,
GError **error)
{
g_return_val_if_fail (data != NULL, FALSE);
/* NSS apparently doesn't do PKCS#8 natively, but you have to put the
* PKCS#8 key into a PKCS#12 file and import that?? So until we figure
* all that out, we can only assume the password is valid.
*/
return TRUE;
}
gboolean
crypto_randomize (void *buffer, gsize buffer_len, GError **error)
{

View file

@ -84,6 +84,9 @@ check-local: test-settings-defaults test-crypto test-secrets
# Private key by itself (PEM)
$(abs_builddir)/test-setting-8021x $(srcdir)/certs/test-key-only.pem "test"
# PKCS#8 private key by itself (PEM)
$(abs_builddir)/test-setting-8021x $(srcdir)/certs/pkcs8-enc-key.pem "1234567890"
# Private key and CA certificate in the same file (pkcs12)
$(abs_builddir)/test-setting-8021x $(srcdir)/certs/test-cert.p12 "test"
@ -126,5 +129,10 @@ check-local: test-settings-defaults test-crypto test-secrets
# Another PKCS#12 file
$(abs_builddir)/test-crypto --p12 $(srcdir)/certs/test2-cert.p12 "12345testing"
# PKCS#8 encrypted private key
$(abs_builddir)/test-crypto --pkcs8 \
$(srcdir)/certs/pkcs8-enc-key.pem \
"1234567890"
endif

View file

@ -17,5 +17,8 @@ EXTRA_DIST = \
test2-cert.p12 \
ca-no-ending-newline.pem \
test-key-only.pem \
test-key-only-decrypted.der
test-key-only-decrypted.der \
pkcs8-enc-key.pem \
pkcs8-noenc-key.pem \
pkcs8-decrypted.der

Binary file not shown.

View file

@ -0,0 +1,29 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6TAbBgkqhkiG9w0BBQMwDgQIwiGFT4Jz6RsCAggABIIEyJNMddDYofbhydUp
J3VyrYIjv3LziJ7dkTXE3+mEYRCQrGLgljWBbib2JOLVCFt8melL6Yv1RcoVR7X7
vrRqyycu0DumI4f5+Bf4wc234JNVhSaLYsw244fFtcnK2Gyn4IaVmWmrNvrwfX/w
SKcVmO30D5C5PCKzv2bou5FmnJLKdDQV1t816cr9T8pTx7MHvBzSZXbh86334BhF
T3zNwo8j2/+Gq2NBWUn+2GTTV8/r26aIwPcFi4QH6I2ghBwFmFHqU3/PoRm6nkmg
CqJj2Dggy+8zE5qg0iId7lrio0OjCH+Qed6NGwIa2lgv/bhuJVP3FOk4gqamJWHi
WMaq9McmS+03q2iokYeSQGbx85x+I90RTFZKhFx4dkerf6oTC/YoL4F++ff0e91v
sOrQsBkgRhrRtFwa9OFCzbsknlixONdd+ITkyX490xz1wcZTDkKtMDRLIPWa2O0b
MEq75jPYThZ5pF1vc5r+rqPafN7SfI+DDmhzJYEQNRoCWA4pH9Gwv0ayKnOgoj4K
TuFhXvcyWzTnVXmcqEFyf3CRrB0Ti+Z61enupC+FCuYV5lGsx9kJaTumTk2UPD02
9Ap3asDLozdEPSXBG3+oCM2s01/IJlxtR84C97r9rpmWTc9K6DCBScETe9KnIghW
PU7XFogueG5Gwpe+x+IlTDq+qiyUNVX1uMGDcIaCC3VsoWqZrpnGGBhsovwBaXKt
T9fT2nE27Fd6DRWso4fgos6PPx7RVveu17BTMVQeUq9L8GrV4JNrE3a9aoXdbUhc
6gMiyAqxh/HEyciYoXsR9oVNi+VM0y8q3hL5nIcgDrCZr/c9aQ8+fuQBDXRrmrQd
bR2iwNLCBnbmQmM/vM333VhJ4MSOKd3SGw/j41K+Nr3uP5KRZUwV+5yy3ef/hGxU
i9JjCmSUt2bfWRUFlNaf1hCTYaKD0xnVr1SLFU4snIgh2qKawyqVc9EE2f+FcOM5
0RtwQ3ku6FOk3cy6/xeKpResCHbWDS6nQaIKYyLukV+gm5MJIhOMkj2z4T1eXGUr
Nu/L7Gz+ps7ct0lM8W82n5lzSEa5/l1eNGM0wtQoAwutFEZp7Nx/IBKK87jVttr6
82UVJeRk7rO2Mpobfw2LbKwga4rsuLrx3UwVDBWdLx7dNIc1rGoAxhsc72+skFgF
Uztwy4Yv1Uiji4T6v+mObPZD/HiIDL0vF02Pz08rNlgB0DgaTKrpql2FutIuQAdf
AciffQIoh9VGERlJoWuunG/UTxg2XRl2m1vCDrgBMInax+PXCv7/5Vh21AQc3fWP
uf4k6JSy46hYni7VTVKn6C/Di9z7oIrGl/jDkDsaenAbToyX9VWr3s7EBwnhTQ/I
OQ9bkWCagHIQlwJbu4M4/VAbiR26NrcR0C3JXBlPlT0qvFFB8gKbJAQEXtwIFS2h
m2fe0k6mQASMwdbJYXZ/wfsg5PPAWsKtny1aMvi0mTPSD5uRhIfEGEuR+AT4UbEW
BkEIE0lgGly4P1SpunKDQQE6m/e7h8Nl4pi8SMSme3YoX5MJwCP/CNkLBDVenAZI
oBrdoVox86SjwnUozVG192lcEAULlk+3ZGt6T9JXLBQl9hpNtyTC6SFh84R+5RoN
AevNl1bDfO+Vci0uJw==
-----END ENCRYPTED PRIVATE KEY-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC80HGgFdlFIL2F
W5rHQ99xOkMrWcZ7an9YurDebAE3M0UwqxM24+5mWbxc8FJ8yYugdMzuI5Nq77IA
DwJpv9ZjMfnKC1VDMj3bmmMdPurfx8pLdLw/jhk3wBaYk6vMvh4z66Yvmb7valGq
Pxh6CRTnekNGI9XA7me4JNBlisl0Qasn9g4pl9PiGJAruEamS4Pk6dSWbfw58bs7
/Yo5ejyt/Mn1n0Q/r3Gv/lAS0qRvmrW6GE1rMtANMfuGRNlAln21TzNcJzykm+sp
RWptxXEI0NY7u9+RP4M3C1mJxWir7AZDbtinpOZH6vF+92yMzgEYOLK/WZVdSPdW
tROv9xtPAgMBAAECggEBAJAfp+vjYZJjuGaYEuAxjdhW+biYcWn5U7V9484TsSXa
i+DnZOZMO8iCjMaAZuZ7zYmwPlE0dK12w29KBbSEy3eySRIRboa5TgBXq3pCcXRZ
g6/vLlZw+AzXIiha6BODt3g4UwUYnWcQx79lJCDa18sNR1a9ucbn8+Har/wiYT3M
JjTbUT6wR6rKEXchB58ZugYGhOTfugSDQg4U/dwEHPIaJ/wme++JUV5B/tjeGCG3
F43o2Oos5vjfrDSpUKIYZn+2BdhP434jkwj22wQ2sy0ruU/kQx8nogMTRfP1v4GU
9QmNXj/DB24K388ZxcDmcxBJxrGAJ0MohYFo28DqRBECgYEA6hyKEqe2UbJx/+B6
8mYgHb+pS2j0M4jPl11q9MMLVxLnDY9xZ85IEyWHQEC0GavPSAois0oiDeGAm32c
j6TFyV3/oPTmZSyV93/agWgnH9Xtc481pbNAb0GMfyotvRRE/+6ti9+Cl7oH9Qmm
ldMk7Hn6sK9t2mUOW8idPjKqlqcCgYEAzne25BryLJoIinbRMZg9KTfxfgUE6EKc
Tk5+9CFQn0/AItQJuKbIUyggYH4psWW5hWq6hFlmMYMR48FKv9ry7pZTB0djaoYD
lN+wSuhzUYWXedkAjvPmekITmf6rbnPfwOZvsr8CGMEUekqJPnPLzsQy+Ea2y/fb
QY4SHe7gExkCgYEAr+1scOJpZvFjK7ckjT3jipd6ADpJsORxo7zG4FImFnQU/6K4
xRpGHWVJQyaccOIkrW04cGUYPDgmrjJx0ZwwKceijvEaphMgS1JgAHklVY4sl3ea
CAAxPqoSi4lFv94Yj/9rmT4IZD6fNivfbJ20FKUBl37tXX4tkRmr2I64lOcCgYEA
x3eqzrclrmdlxvfBZOuScwbkHP6WXhk0TwbQ6eRhsnfmxP8bITSoJoaGuRJKD2Oa
l0WkSobgDwd0uhecsrvBpTS/pDGY32n3fdWZyNTHzEOHMyWtv23tBcJek5ERaBU0
X3WBBiw4x1eKBBeMfjR6+xhbsbcHlQiw36V05UxJWMkCgYEAhtcYvrfU4K48IJTU
qp03nvd+dMY3IUTdZNOCh8bswLKyn3aq3MfWF9Vp7kDAI3cfyMpSrAQnmg4nVcn6
Gf3wakG8bpiSRbJnGN+iLm8JsD+3Vw9KzvKOOQVmpT7xt5Kupx1hWvLHQWvfYgOG
qEtTM8/+LD7W3I7midJNt50CD8A=
-----END PRIVATE KEY-----

View file

@ -238,6 +238,29 @@ test_is_pkcs12 (const char *path, gboolean expect_fail, const char *desc)
}
}
static void
test_load_pkcs8 (const char *path,
const char *password,
gboolean expect_fail,
const char *desc)
{
NMCryptoFileFormat format = NM_CRYPTO_FILE_FORMAT_UNKNOWN;
GError *error = NULL;
format = crypto_verify_private_key (path, password, &error);
if (expect_fail) {
ASSERT (format == NM_CRYPTO_FILE_FORMAT_UNKNOWN, desc,
"unexpected success reading PKCS#8 private key file "
"'%s' with invalid password",
path);
} else {
ASSERT (format == NM_CRYPTO_FILE_FORMAT_RAW_KEY, desc,
"%s: unexpected PKCS#8 private key file format (expected %d, got "
"%d): %d %s",
path, NM_CRYPTO_FILE_FORMAT_RAW_KEY, format, error->code, error->message);
}
}
static void
test_encrypt_private_key (const char *path,
const char *password,
@ -316,6 +339,17 @@ int main (int argc, char **argv)
test_load_pkcs12 (argv[2], argv[3], FALSE, "pkcs12-private-key");
test_load_pkcs12 (argv[2], "blahblahblah", TRUE, "pkcs12-private-key-bad-password");
test_load_pkcs12_no_password (argv[2], "pkcs12-private-key-no-password");
} else if (!strcmp (argv[1], "--pkcs8")) {
ASSERT (argc == 4, "test-crypto",
"wrong number of arguments (--pkcs8 <key file> <password>)");
test_is_pkcs12 (argv[2], TRUE, "not-pkcs12");
test_load_pkcs8 (argv[2], argv[3], FALSE, "pkcs8-private-key");
/* Until gnutls and NSS grow support for all the ciphers that openssl
* can use with PKCS#8, we can't actually verify the password. So we
* expect a bad password to work for the time being.
*/
test_load_pkcs8 (argv[2], "blahblahblah", FALSE, "pkcs8-private-key-bad-password");
} else {
ASSERT (argc > 2, "test-crypto", "unknown test type (not --cert, --key, or --p12)");
}