os400qc3: support encrypted private keys

PKCS#8 EncryptedPrivateKeyinfo structures are recognized and decoded to get
values accepted by the Qc3 crypto library.
diff --git a/src/os400qc3.c b/src/os400qc3.c
index fb37151..f8e46ab 100644
--- a/src/os400qc3.c
+++ b/src/os400qc3.c
@@ -1788,18 +1788,143 @@
 }
 
 static int
+pkcs8kek(LIBSSH2_SESSION *session, _libssh2_os400qc3_crypto_ctx **ctx,
+         const unsigned char *data, unsigned int datalen,
+         const unsigned char *passphrase, asn1Element *privkeyinfo)
+{
+    asn1Element encprivkeyinfo;
+    asn1Element pkcs5alg;
+    pkcs5params pkcs5;
+    size_t pplen;
+    char *cp;
+    unsigned long t;
+    int i;
+    char *dk = NULL;
+    Qc3_Format_ALGD0200_T algd;
+    Qus_EC_t errcode;
+
+    /* Determine if the PKCS#8 data is encrypted and, if so, set-up a
+       key encryption key and algorithm in context.
+       Return 1 if encrypted, 0, if not, -1 if error. */
+
+    *ctx = NULL;
+    privkeyinfo->beg = (char *) data;
+    privkeyinfo->end = privkeyinfo->beg + datalen;
+
+    /* If no passphrase is given, it cannot be an encrypted key. */
+    if (!passphrase || !*passphrase)
+        return 0;
+
+    /* Parse PKCS#8 data, checking if ASN.1 format is PrivateKeyInfo or
+       EncryptedPrivateKeyInfo. */
+    if (getASN1Element(&encprivkeyinfo, privkeyinfo->beg, privkeyinfo->end) !=
+        (char *) data + datalen ||
+        *encprivkeyinfo.header != (ASN1_SEQ | ASN1_CONSTRUCTED))
+        return -1;
+    cp = getASN1Element(&pkcs5alg, encprivkeyinfo.beg, encprivkeyinfo.end);
+    if (!cp)
+        return -1;
+
+    switch (*pkcs5alg.header) {
+    case ASN1_INTEGER:                          /* Version. */
+        return 0;       /* This is a PrivateKeyInfo --> not encrypted. */
+    case ASN1_SEQ | ASN1_CONSTRUCTED:           /* AlgorithIdentifier. */
+        break;          /* This is an EncryptedPrivateKeyInfo --> encrypted. */
+    default:
+        return -1;      /* Unrecognized: error. */
+    }
+
+    /* Get the encrypted key data. */
+    if (getASN1Element(privkeyinfo, cp, encprivkeyinfo.end) !=
+        encprivkeyinfo.end || *privkeyinfo->header != ASN1_OCTET_STRING)
+        return -1;
+
+    /* PKCS#5: parse the PBES AlgorithmIdentifier and recursively get all
+       encryption parameters. */
+    memset((char *) &pkcs5, 0, sizeof pkcs5);
+    if (parse_pkcs5_algorithm(session, &pkcs5, &pkcs5alg, pbestable))
+        return -1;
+
+    /* Compute the derived key. */
+    if ((*pkcs5.kdf)(session, &dk, passphrase, &pkcs5))
+        return -1;
+
+    /* Prepare the algorithm descriptor. */
+    memset((char *) &algd, 0, sizeof algd);
+    algd.Block_Cipher_Alg = pkcs5.cipher;
+    algd.Block_Length = pkcs5.blocksize;
+    algd.Mode = pkcs5.mode;
+    algd.Pad_Option = pkcs5.padopt;
+    algd.Pad_Character = pkcs5.padchar;
+    algd.Effective_Key_Size = pkcs5.effkeysize;
+    memcpy(algd.Init_Vector, pkcs5.iv, pkcs5.ivlen);
+
+    /* Create the key and algorithm context tokens. */
+    *ctx = libssh2_init_crypto_ctx(NULL);
+    if (!*ctx) {
+        LIBSSH2_FREE(session, dk);
+        return -1;
+    }
+    libssh2_init_crypto_ctx(*ctx);
+    set_EC_length(errcode, sizeof errcode);
+    Qc3CreateKeyContext(dk, &pkcs5.dklen, binstring, &algd.Block_Cipher_Alg,
+                        qc3clear, NULL, NULL, (*ctx)->key.Key_Context_Token,
+                        (char *) &errcode);
+    LIBSSH2_FREE(session, dk);
+    if (errcode.Bytes_Available) {
+        free((char *) *ctx);
+        *ctx = NULL;
+        return -1;
+    }
+
+    Qc3CreateAlgorithmContext((char *) &algd, Qc3_Alg_Block_Cipher,
+                              (*ctx)->hash.Alg_Context_Token, &errcode);
+    if (errcode.Bytes_Available) {
+        Qc3DestroyKeyContext((*ctx)->key.Key_Context_Token, (char *) &ecnull);
+        free((char *) *ctx);
+        *ctx = NULL;
+        return -1;
+    }
+    return 1;       /* Tell it's encrypted. */
+}
+
+static int
 rsapkcs8privkey(LIBSSH2_SESSION *session,
                 const unsigned char *data, unsigned int datalen,
                 const unsigned char *passphrase, void *loadkeydata)
 {
     libssh2_rsa_ctx *ctx = (libssh2_rsa_ctx *) loadkeydata;
+    char keyform = Qc3_Clear;
+    char *kek = NULL;
+    char *kea = NULL;
+    _libssh2_os400qc3_crypto_ctx *kekctx;
+    asn1Element pki;
+    int pkilen;
     Qus_EC_t errcode;
 
+    switch (pkcs8kek(session, &kekctx, data, datalen, passphrase, &pki)) {
+    case 1:
+        keyform = Qc3_Encrypted;
+        kek = kekctx->key.Key_Context_Token;
+        kea = kekctx->hash.Alg_Context_Token;
+    case 0:
+        break;
+    default:
+        return -1;
+    }
+
     set_EC_length(errcode, sizeof errcode);
-    Qc3CreateKeyContext((unsigned char *) data, (int *) &datalen, berstring,
-                        rsaprivate, qc3clear, NULL, NULL,
+    pkilen = pki.end - pki.beg;
+    Qc3CreateKeyContext((unsigned char *) pki.beg, &pkilen, berstring,
+                        rsaprivate, &keyform, kek, kea,
                         ctx->key.Key_Context_Token, (char *) &errcode);
-    return errcode.Bytes_Available? -1: 0;
+    if (errcode.Bytes_Available) {
+        if (kekctx)
+            _libssh2_os400qc3_crypto_dtor(kekctx);
+        return -1;
+    }
+    ctx->kek = kekctx;
+    return 0;
 }
 
 static char *
@@ -1852,18 +1977,38 @@
     int len;
     char *cp;
     int i;
+    char keyform = Qc3_Clear;
+    char *kek = NULL;
+    char *kea = NULL;
+    _libssh2_os400qc3_crypto_ctx *kekctx;
     asn1Element subjpubkeyinfo;
     asn1Element algorithmid;
     asn1Element algorithm;
     asn1Element subjpubkey;
     asn1Element parameters;
+    asn1Element pki;
+    int pkilen;
     Qus_EC_t errcode;
 
     if (!(buf = alloca(datalen)))
         return -1;
+
+    switch (pkcs8kek(session, &kekctx, data, datalen, passphrase, &pki)) {
+    case 1:
+        keyform = Qc3_Encrypted;
+        kek = kekctx->key.Key_Context_Token;
+        kea = kekctx->hash.Alg_Context_Token;
+    case 0:
+        break;
+    default:
+        return -1;
+    }
+
     set_EC_length(errcode, sizeof errcode);
-    Qc3ExtractPublicKey((char *) data, (int *) &datalen, berstring, qc3clear,
-                        NULL, NULL, buf, (int *) &datalen, &len, &errcode);
+    pkilen = pki.end - pki.beg;
+    Qc3ExtractPublicKey(pki.beg, &pkilen, berstring, &keyform,
+                        kek, kea, buf, (int *) &datalen, &len, &errcode);
+    _libssh2_os400qc3_crypto_dtor(kekctx);
     if (errcode.Bytes_Available)
         return -1;
     /* Get the algorithm OID and key data from SubjectPublicKeyInfo. */
@@ -2108,7 +2253,7 @@
     *method_len = 0;
     *pubkeydata = NULL;
     *pubkeydata_len = 0;
-    /* Note: passphrase not supported. */
+
     ret = load_rsa_private_file(session, privatekey, passphrase,
                                 rsapkcs1pubkey, rsapkcs8pubkey, (void *) &p);
     if (!ret) {