mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-04-17 15:00:43 +02:00
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:
parent
5737573314
commit
5deb993ff6
10 changed files with 264 additions and 5 deletions
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
BIN
libnm-util/tests/certs/pkcs8-decrypted.der
Normal file
BIN
libnm-util/tests/certs/pkcs8-decrypted.der
Normal file
Binary file not shown.
29
libnm-util/tests/certs/pkcs8-enc-key.pem
Normal file
29
libnm-util/tests/certs/pkcs8-enc-key.pem
Normal 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-----
|
||||
28
libnm-util/tests/certs/pkcs8-noenc-key.pem
Normal file
28
libnm-util/tests/certs/pkcs8-noenc-key.pem
Normal 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-----
|
||||
|
|
@ -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)");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue