|
@@ -9,8 +9,66 @@
|
|
|
|
|
|
#ifdef LTC_PEM
|
|
|
|
|
|
+struct dek_info_from_str {
|
|
|
+ const struct str id;
|
|
|
+ struct dek_info info;
|
|
|
+};
|
|
|
+
|
|
|
/* Encrypted PEM files */
|
|
|
-#define PEM_DECODE_BUFSZ 72
|
|
|
+static const struct str proc_type_encrypted = { SET_CSTR(, "Proc-Type: 4,ENCRYPTED") };
|
|
|
+static const struct dek_info_from_str dek_infos[] =
|
|
|
+ {
|
|
|
+ { SET_CSTR(.id, "DEK-Info: AES-128-CBC,"), .info.alg = "aes", .info.keylen = 128 / 8, },
|
|
|
+ { SET_CSTR(.id, "DEK-Info: AES-192-CBC,"), .info.alg = "aes", .info.keylen = 192 / 8, },
|
|
|
+ { SET_CSTR(.id, "DEK-Info: AES-256-CBC,"), .info.alg = "aes", .info.keylen = 256 / 8, },
|
|
|
+ { SET_CSTR(.id, "DEK-Info: CAMELLIA-128-CBC,"), .info.alg = "camellia", .info.keylen = 128 / 8, },
|
|
|
+ { SET_CSTR(.id, "DEK-Info: CAMELLIA-192-CBC,"), .info.alg = "camellia", .info.keylen = 192 / 8, },
|
|
|
+ { SET_CSTR(.id, "DEK-Info: CAMELLIA-256-CBC,"), .info.alg = "camellia", .info.keylen = 256 / 8, },
|
|
|
+ { SET_CSTR(.id, "DEK-Info: DES-EDE3-CBC,"), .info.alg = "3des", .info.keylen = 192 / 8, },
|
|
|
+ { SET_CSTR(.id, "DEK-Info: DES-CBC,"), .info.alg = "des", .info.keylen = 64 / 8, },
|
|
|
+ };
|
|
|
+
|
|
|
+static int s_decrypt_pem(unsigned char *pem, unsigned long *l, const struct pem_headers *hdr)
|
|
|
+{
|
|
|
+ unsigned char iv[MAXBLOCKSIZE], key[MAXBLOCKSIZE];
|
|
|
+ unsigned long ivlen, klen;
|
|
|
+ int err;
|
|
|
+ symmetric_CBC cbc_ctx;
|
|
|
+
|
|
|
+ if (hdr->info.keylen > sizeof(key)) {
|
|
|
+ return CRYPT_BUFFER_OVERFLOW;
|
|
|
+ }
|
|
|
+ if (!hdr->pw->pw) {
|
|
|
+ return CRYPT_INVALID_ARG;
|
|
|
+ }
|
|
|
+
|
|
|
+ ivlen = sizeof(iv);
|
|
|
+ if ((err = base16_decode(hdr->info.iv, XSTRLEN(hdr->info.iv), iv, &ivlen)) != CRYPT_OK) {
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+ klen = hdr->info.keylen;
|
|
|
+ if ((err = pkcs_5_alg1_openssl(hdr->pw->pw, hdr->pw->l, iv, 1, find_hash("md5"), key, &klen))) {
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((err = cbc_start(find_cipher(hdr->info.alg), iv, key, klen, 0, &cbc_ctx)) != CRYPT_OK) {
|
|
|
+ goto error_out;
|
|
|
+ }
|
|
|
+ if ((err = cbc_decrypt(pem, pem, *l, &cbc_ctx)) != CRYPT_OK) {
|
|
|
+ goto error_out;
|
|
|
+ }
|
|
|
+ if ((err = cbc_done(&cbc_ctx)) != CRYPT_OK) {
|
|
|
+ goto error_out;
|
|
|
+ }
|
|
|
+ if ((err = padding_depad(pem, l, LTC_PAD_PKCS7 | cbc_ctx.blocklen)) != CRYPT_OK) {
|
|
|
+ goto error_out;
|
|
|
+ }
|
|
|
+
|
|
|
+error_out:
|
|
|
+ zeromem(key, sizeof(key));
|
|
|
+ zeromem(iv, sizeof(iv));
|
|
|
+ return err;
|
|
|
+}
|
|
|
|
|
|
int pem_get_char_from_file(struct get_char *g)
|
|
|
{
|
|
@@ -28,10 +86,27 @@ int pem_get_char_from_buf(struct get_char *g)
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static void s_unget_line(char *buf, unsigned long buflen, struct get_char *g)
|
|
|
+{
|
|
|
+ if (buflen > sizeof(g->unget_buf_))
|
|
|
+ return;
|
|
|
+ g->unget_buf.p = g->unget_buf_;
|
|
|
+ COPY_STR(g->unget_buf, buf, buflen);
|
|
|
+}
|
|
|
+
|
|
|
static char* s_get_line(char *buf, unsigned long *buflen, struct get_char *g)
|
|
|
{
|
|
|
unsigned long blen = 0;
|
|
|
int c = -1, c_;
|
|
|
+ if (g->unget_buf.p) {
|
|
|
+ if (*buflen < g->unget_buf.len) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+ XMEMCPY(buf, g->unget_buf.p, g->unget_buf.len);
|
|
|
+ *buflen = g->unget_buf.len;
|
|
|
+ FREE_STR(g->unget_buf);
|
|
|
+ return buf;
|
|
|
+ }
|
|
|
while(blen < *buflen) {
|
|
|
c_ = c;
|
|
|
c = g->get(g);
|
|
@@ -66,12 +141,66 @@ static LTC_INLINE int s_fits_buf(void *dest, unsigned long to_write, void *end)
|
|
|
|
|
|
static int s_pem_decode_headers(struct pem_headers *hdr, struct get_char *g)
|
|
|
{
|
|
|
+ char buf[LTC_PEM_DECODE_BUFSZ];
|
|
|
+ unsigned long slen, tmplen, n;
|
|
|
+ int has_more_headers = hdr->id->has_more_headers == no ? 0 : 3;
|
|
|
+
|
|
|
+ /* Make sure the PEM has the appropriate extension headers if required.
|
|
|
+ *
|
|
|
+ * ```
|
|
|
+ * Proc-Type: 4,ENCRYPTED[\r]\n
|
|
|
+ * DEK-Info: <algorithm>,<IV>[\r]\n
|
|
|
+ * [\r]\n
|
|
|
+ * ```
|
|
|
+ */
|
|
|
+ while (has_more_headers) {
|
|
|
+ slen = sizeof(buf);
|
|
|
+ if (!s_get_line(buf, &slen, g) || (has_more_headers > 1 && slen == 0)) {
|
|
|
+ return CRYPT_INVALID_PACKET;
|
|
|
+ }
|
|
|
+ switch (has_more_headers) {
|
|
|
+ case 3:
|
|
|
+ if (XMEMCMP(buf, proc_type_encrypted.p, proc_type_encrypted.len)) {
|
|
|
+ s_unget_line(buf, slen, g);
|
|
|
+ if (hdr->id->has_more_headers == maybe)
|
|
|
+ return CRYPT_OK;
|
|
|
+ else
|
|
|
+ return CRYPT_INVALID_PACKET;
|
|
|
+ }
|
|
|
+ hdr->encrypted = 1;
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ hdr->info.alg = NULL;
|
|
|
+ for (n = 0; n < sizeof(dek_infos)/sizeof(dek_infos[0]); ++n) {
|
|
|
+ if (slen >= dek_infos[n].id.len && !XMEMCMP(buf, dek_infos[n].id.p, dek_infos[n].id.len)) {
|
|
|
+ hdr->info = dek_infos[n].info;
|
|
|
+ tmplen = XSTRLEN(buf + dek_infos[n].id.len);
|
|
|
+ if (tmplen > sizeof(hdr->info.iv))
|
|
|
+ return CRYPT_INVALID_KEYSIZE;
|
|
|
+ XMEMCPY(hdr->info.iv, buf + dek_infos[n].id.len, tmplen);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (hdr->info.alg == NULL) {
|
|
|
+ return CRYPT_INVALID_CIPHER;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 1:
|
|
|
+ /* Make sure that there's an empty line in between */
|
|
|
+ if (buf[0] != '\0')
|
|
|
+ return CRYPT_INVALID_PACKET;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return CRYPT_INVALID_CIPHER;
|
|
|
+ }
|
|
|
+ has_more_headers--;
|
|
|
+ }
|
|
|
return CRYPT_OK;
|
|
|
}
|
|
|
|
|
|
int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_char *g)
|
|
|
{
|
|
|
- char buf[PEM_DECODE_BUFSZ];
|
|
|
+ char buf[LTC_PEM_DECODE_BUFSZ];
|
|
|
char *wpem = pem;
|
|
|
char *end = wpem + *w;
|
|
|
unsigned long slen, linelen;
|
|
@@ -82,17 +211,19 @@ int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_ch
|
|
|
if (s_get_line(buf, &linelen, g) == NULL) {
|
|
|
return CRYPT_INVALID_PACKET;
|
|
|
}
|
|
|
- if (hdr->start.len != linelen || XMEMCMP(buf, hdr->start.p, hdr->start.len)) {
|
|
|
+ if (hdr->id->start.len != linelen || XMEMCMP(buf, hdr->id->start.p, hdr->id->start.len)) {
|
|
|
+ s_unget_line(buf, linelen, g);
|
|
|
return CRYPT_INVALID_PACKET;
|
|
|
}
|
|
|
|
|
|
+ hdr->encrypted = hdr->id->encrypted;
|
|
|
if ((err = s_pem_decode_headers(hdr, g)) != CRYPT_OK)
|
|
|
return err;
|
|
|
|
|
|
/* Read the base64 encoded part of the PEM */
|
|
|
slen = sizeof(buf);
|
|
|
while (s_get_line(buf, &slen, g)) {
|
|
|
- if (slen == hdr->end.len && !XMEMCMP(buf, hdr->end.p, slen)) {
|
|
|
+ if (slen == hdr->id->end.len && !XMEMCMP(buf, hdr->id->end.p, slen)) {
|
|
|
hdr_ok = 1;
|
|
|
break;
|
|
|
}
|
|
@@ -126,4 +257,156 @@ int pem_read(void *pem, unsigned long *w, struct pem_headers *hdr, struct get_ch
|
|
|
return CRYPT_OK;
|
|
|
}
|
|
|
|
|
|
+static const struct pem_header_id pem_std_headers[] = {
|
|
|
+ {
|
|
|
+ /* PKCS#8 encrypted */
|
|
|
+ SET_CSTR(.start, "-----BEGIN ENCRYPTED PRIVATE KEY-----"),
|
|
|
+ SET_CSTR(.end, "-----END ENCRYPTED PRIVATE KEY-----"),
|
|
|
+ .has_more_headers = no,
|
|
|
+ .encrypted = 1,
|
|
|
+ .pkcs8 = 1,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ /* PKCS#8 plain */
|
|
|
+ SET_CSTR(.start, "-----BEGIN PRIVATE KEY-----"),
|
|
|
+ SET_CSTR(.end, "-----END PRIVATE KEY-----"),
|
|
|
+ .has_more_headers = no,
|
|
|
+ .pkcs8 = 1,
|
|
|
+ },
|
|
|
+ /* Regular plain or encrypted private keys */
|
|
|
+ {
|
|
|
+ SET_CSTR(.start, "-----BEGIN RSA PRIVATE KEY-----"),
|
|
|
+ SET_CSTR(.end, "-----END RSA PRIVATE KEY-----"),
|
|
|
+ .has_more_headers = maybe,
|
|
|
+ .pka = LTC_PKA_RSA,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ SET_CSTR(.start, "-----BEGIN EC PRIVATE KEY-----"),
|
|
|
+ SET_CSTR(.end, "-----END EC PRIVATE KEY-----"),
|
|
|
+ .has_more_headers = maybe,
|
|
|
+ .pka = LTC_PKA_EC,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ SET_CSTR(.start, "-----BEGIN DSA PRIVATE KEY-----"),
|
|
|
+ SET_CSTR(.end, "-----END DSA PRIVATE KEY-----"),
|
|
|
+ .has_more_headers = maybe,
|
|
|
+ .pka = LTC_PKA_DSA,
|
|
|
+ },
|
|
|
+};
|
|
|
+typedef int (*pkcs8_import)(const unsigned char *in, unsigned long inlen,
|
|
|
+ password_ctx *pw_ctx,
|
|
|
+ void *key);
|
|
|
+typedef struct {
|
|
|
+ enum ltc_oid_id id;
|
|
|
+ pkcs8_import fn;
|
|
|
+} p8_import_st;
|
|
|
+
|
|
|
+static int s_decode(struct get_char *g, ltc_pka_key *k, password_ctx *pw_ctx)
|
|
|
+{
|
|
|
+ unsigned char *pem = NULL;
|
|
|
+ unsigned long w, l, n;
|
|
|
+ int err = CRYPT_ERROR;
|
|
|
+ struct pem_headers hdr = { 0 };
|
|
|
+ struct password pw;
|
|
|
+ ltc_asn1_list *p8_asn1 = NULL;
|
|
|
+ w = LTC_PEM_READ_BUFSIZE * 2;
|
|
|
+retry:
|
|
|
+ pem = XREALLOC(pem, w);
|
|
|
+ for (n = 0; n < sizeof(pem_std_headers)/sizeof(pem_std_headers[0]); ++n) {
|
|
|
+ hdr.id = &pem_std_headers[n];
|
|
|
+ err = pem_read(pem, &w, &hdr, g);
|
|
|
+ if (err == CRYPT_BUFFER_OVERFLOW) {
|
|
|
+ goto retry;
|
|
|
+ } else if (err == CRYPT_OK) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ hdr.id = NULL;
|
|
|
+ }
|
|
|
+ /* id not found */
|
|
|
+ if (hdr.id == NULL)
|
|
|
+ goto cleanup;
|
|
|
+ l = w;
|
|
|
+ if (hdr.id->pkcs8) {
|
|
|
+ enum ltc_oid_id pka;
|
|
|
+ ltc_asn1_list *alg_id, *priv_key;
|
|
|
+ if ((err = pkcs8_decode_flexi(pem, l, pw_ctx, &p8_asn1)) != CRYPT_OK) {
|
|
|
+ goto cleanup;
|
|
|
+ }
|
|
|
+ if ((err = pkcs8_get_children(p8_asn1, &pka, &alg_id, &priv_key)) != CRYPT_OK) {
|
|
|
+ goto cleanup;
|
|
|
+ }
|
|
|
+ switch (pka) {
|
|
|
+ case LTC_OID_RSA:
|
|
|
+ err = rsa_import_pkcs8_asn1(alg_id, priv_key, &k->u.rsa);
|
|
|
+ k->id = LTC_PKA_RSA;
|
|
|
+ break;
|
|
|
+ case LTC_OID_EC:
|
|
|
+ err = ecc_import_pkcs8_asn1(alg_id, priv_key, &k->u.ecc);
|
|
|
+ k->id = LTC_PKA_EC;
|
|
|
+ break;
|
|
|
+ case LTC_OID_ED25519:
|
|
|
+ err = ed25519_import_pkcs8_asn1(alg_id, priv_key, &k->u.curve25519);
|
|
|
+ k->id = LTC_PKA_CURVE25519;
|
|
|
+ break;
|
|
|
+ case LTC_OID_X25519:
|
|
|
+ err = x25519_import_pkcs8_asn1(alg_id, priv_key, &k->u.curve25519);
|
|
|
+ k->id = LTC_PKA_CURVE25519;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ err = CRYPT_PK_INVALID_TYPE;
|
|
|
+ }
|
|
|
+ goto cleanup;
|
|
|
+ } else if (hdr.encrypted) {
|
|
|
+ LTC_ARGCHK(pw_ctx != NULL);
|
|
|
+ LTC_ARGCHK(pw_ctx->callback != NULL);
|
|
|
+
|
|
|
+ hdr.pw = &pw;
|
|
|
+ if (pw_ctx->callback(&hdr.pw->pw, &hdr.pw->l, pw_ctx->userdata)) {
|
|
|
+ err = CRYPT_ERROR;
|
|
|
+ goto cleanup;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((err = s_decrypt_pem(pem, &l, &hdr)) != CRYPT_OK) {
|
|
|
+ goto cleanup;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ switch (hdr.id->pka) {
|
|
|
+ case LTC_OID_RSA:
|
|
|
+ err = rsa_import(pem, l, &k->u.rsa);
|
|
|
+ k->id = LTC_PKA_RSA;
|
|
|
+ break;
|
|
|
+ case LTC_OID_EC:
|
|
|
+ err = ecc_import_openssl(pem, l, &k->u.ecc);
|
|
|
+ k->id = LTC_PKA_EC;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ err = CRYPT_PK_INVALID_TYPE;
|
|
|
+ goto cleanup;
|
|
|
+ }
|
|
|
+
|
|
|
+cleanup:
|
|
|
+ if (p8_asn1) {
|
|
|
+ der_sequence_free(p8_asn1);
|
|
|
+ }
|
|
|
+ if (hdr.pw) {
|
|
|
+ zeromem(hdr.pw->pw, hdr.pw->l);
|
|
|
+ XFREE(hdr.pw->pw);
|
|
|
+ }
|
|
|
+ XFREE(pem);
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+int pem_decode_filehandle(FILE *f, ltc_pka_key *k, password_ctx *pw_ctx)
|
|
|
+{
|
|
|
+ struct get_char g = { .get = pem_get_char_from_file, .f = f };
|
|
|
+ return s_decode(&g, k, pw_ctx);
|
|
|
+}
|
|
|
+
|
|
|
+int pem_decode(const void *buf, unsigned long len, ltc_pka_key *k, password_ctx *pw_ctx)
|
|
|
+{
|
|
|
+ struct get_char g = { .get = pem_get_char_from_buf, SET_BUFP(.buf, buf, len) };
|
|
|
+ return s_decode(&g, k, pw_ctx);
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
#endif /* LTC_PEM */
|