mirror of
https://github.com/OpenVPN/openvpn.git
synced 2024-11-23 17:53:49 +08:00
Allow external EC key through --management-external-key
- This automatically supports EC certificates through --management-external-cert - EC signature request from management is prompted by >PK_SIGN if the client supports it (or >RSA_SIGN) Response should be of the form 'pk-sig' (or rsa-sig by older clients) followed by DER encoded signature as base64 terminated by 'END' on a new line. v3: This is v2 adapted to the client_version capability Requires pacthes 1 and 2 of the series 147: https://patchwork.openvpn.net/project/openvpn2/list/?series=147 Signed-off-by: Selva Nair <selva.nair@gmail.com> Acked-by: Arne Schwabe <arne@rfc2549.org> Message-Id: <1516909513-31683-1-git-send-email-selva.nair@gmail.com> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg16365.html Signed-off-by: Gert Doering <gert@greenie.muc.de>
This commit is contained in:
parent
7f7f00da88
commit
7eca140c70
@ -779,14 +779,14 @@ COMMAND -- rsa-sig (OpenVPN 2.3 or higher, management version <= 1)
|
||||
Provides support for external storage of the private key. Requires the
|
||||
--management-external-key option. This option can be used instead of "key"
|
||||
in client mode, and allows the client to run without the need to load the
|
||||
actual private key. When the SSL protocol needs to perform an RSA sign
|
||||
actual private key. When the SSL protocol needs to perform a sign
|
||||
operation, the data to be signed will be sent to the management interface
|
||||
via a notification as follows:
|
||||
|
||||
>PK_SIGN:[BASE64_DATA] (if client announces support for management version > 1)
|
||||
>RSA_SIGN:[BASE64_DATA] (only older clients will be prompted like this)
|
||||
|
||||
The management interface client should then create a PKCS#1 v1.5 signature of
|
||||
The management interface client should then create an appropriate signature of
|
||||
the (decoded) BASE64_DATA using the private key and return the SSL signature as
|
||||
follows:
|
||||
|
||||
@ -797,8 +797,9 @@ pk-sig (or rsa-sig)
|
||||
.
|
||||
END
|
||||
|
||||
Base64 encoded output of RSA_private_encrypt() (OpenSSL) or mbedtls_pk_sign()
|
||||
(mbed TLS) will provide a correct signature.
|
||||
Base64 encoded output of RSA_private_encrypt for RSA or ECDSA_sign() for EC
|
||||
using OpenSSL or mbedtls_pk_sign() using mbed TLS will provide a correct
|
||||
signature.
|
||||
|
||||
This capability is intended to allow the use of arbitrary cryptographic
|
||||
service providers with OpenVPN via the management interface.
|
||||
|
@ -1043,58 +1043,51 @@ openvpn_extkey_rsa_finish(RSA *rsa)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Pass the input hash in 'dgst' to management and get the signature back.
|
||||
* On input siglen contains the capacity of the buffer 'sig'.
|
||||
* On return signature is in sig.
|
||||
* Return value is signature length or -1 on error.
|
||||
*/
|
||||
static int
|
||||
get_sig_from_man(const unsigned char *dgst, unsigned int dgstlen,
|
||||
unsigned char *sig, unsigned int siglen)
|
||||
{
|
||||
char *in_b64 = NULL;
|
||||
char *out_b64 = NULL;
|
||||
int len = -1;
|
||||
|
||||
/* convert 'dgst' to base64 */
|
||||
if (management
|
||||
&& openvpn_base64_encode(dgst, dgstlen, &in_b64) > 0)
|
||||
{
|
||||
out_b64 = management_query_pk_sig(management, in_b64);
|
||||
}
|
||||
if (out_b64)
|
||||
{
|
||||
len = openvpn_base64_decode(out_b64, sig, siglen);
|
||||
}
|
||||
|
||||
free(in_b64);
|
||||
free(out_b64);
|
||||
return len;
|
||||
}
|
||||
|
||||
/* sign arbitrary data */
|
||||
static int
|
||||
rsa_priv_enc(int flen, const unsigned char *from, unsigned char *to, RSA *rsa, int padding)
|
||||
{
|
||||
/* optional app data in rsa->meth->app_data; */
|
||||
char *in_b64 = NULL;
|
||||
char *out_b64 = NULL;
|
||||
unsigned int len = RSA_size(rsa);
|
||||
int ret = -1;
|
||||
int len;
|
||||
|
||||
if (padding != RSA_PKCS1_PADDING)
|
||||
{
|
||||
RSAerr(RSA_F_RSA_OSSL_PRIVATE_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);
|
||||
goto done;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* convert 'from' to base64 */
|
||||
if (openvpn_base64_encode(from, flen, &in_b64) <= 0)
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
ret = get_sig_from_man(from, flen, to, len);
|
||||
|
||||
/* call MI for signature */
|
||||
if (management)
|
||||
{
|
||||
out_b64 = management_query_pk_sig(management, in_b64);
|
||||
}
|
||||
if (!out_b64)
|
||||
{
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* decode base64 signature to binary */
|
||||
len = RSA_size(rsa);
|
||||
ret = openvpn_base64_decode(out_b64, to, len);
|
||||
|
||||
/* verify length */
|
||||
if (ret != len)
|
||||
{
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
done:
|
||||
if (in_b64)
|
||||
{
|
||||
free(in_b64);
|
||||
}
|
||||
if (out_b64)
|
||||
{
|
||||
free(out_b64);
|
||||
}
|
||||
return ret;
|
||||
return (ret == len)? ret : -1;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -1166,6 +1159,130 @@ err:
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_EC)
|
||||
|
||||
/* called when EC_KEY is destroyed */
|
||||
static void
|
||||
openvpn_extkey_ec_finish(EC_KEY *ec)
|
||||
{
|
||||
/* release the method structure */
|
||||
const EC_KEY_METHOD *ec_meth = EC_KEY_get_method(ec);
|
||||
EC_KEY_METHOD_free((EC_KEY_METHOD *) ec_meth);
|
||||
}
|
||||
|
||||
/* EC_KEY_METHOD callback: sign().
|
||||
* Sign the hash using EC key and return DER encoded signature in sig,
|
||||
* its length in siglen. Return value is 1 on success, 0 on error.
|
||||
*/
|
||||
static int
|
||||
ecdsa_sign(int type, const unsigned char *dgst, int dgstlen, unsigned char *sig,
|
||||
unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *ec)
|
||||
{
|
||||
int capacity = ECDSA_size(ec);
|
||||
int len = get_sig_from_man(dgst, dgstlen, sig, capacity);
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
*siglen = len;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* EC_KEY_METHOD callback: sign_setup(). We do no precomputations */
|
||||
static int
|
||||
ecdsa_sign_setup(EC_KEY *ec, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* EC_KEY_METHOD callback: sign_sig().
|
||||
* Sign the hash and return the result as a newly allocated ECDS_SIG
|
||||
* struct or NULL on error.
|
||||
*/
|
||||
static ECDSA_SIG *
|
||||
ecdsa_sign_sig(const unsigned char *dgst, int dgstlen, const BIGNUM *in_kinv,
|
||||
const BIGNUM *in_r, EC_KEY *ec)
|
||||
{
|
||||
ECDSA_SIG *ecsig = NULL;
|
||||
unsigned int len = ECDSA_size(ec);
|
||||
struct gc_arena gc = gc_new();
|
||||
|
||||
unsigned char *buf = gc_malloc(len, false, &gc);
|
||||
if (ecdsa_sign(0, dgst, dgstlen, buf, &len, NULL, NULL, ec) != 1)
|
||||
{
|
||||
goto out;
|
||||
}
|
||||
/* const char ** should be avoided: not up to us, so we cast our way through */
|
||||
ecsig = d2i_ECDSA_SIG(NULL, (const unsigned char **)&buf, len);
|
||||
|
||||
out:
|
||||
gc_free(&gc);
|
||||
return ecsig;
|
||||
}
|
||||
|
||||
static int
|
||||
tls_ctx_use_external_ec_key(struct tls_root_ctx *ctx, EVP_PKEY *pkey)
|
||||
{
|
||||
EC_KEY *ec = NULL;
|
||||
EVP_PKEY *privkey = NULL;
|
||||
EC_KEY_METHOD *ec_method;
|
||||
|
||||
ASSERT(ctx);
|
||||
|
||||
ec_method = EC_KEY_METHOD_new(EC_KEY_OpenSSL());
|
||||
if (!ec_method)
|
||||
{
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Among init methods, we only need the finish method */
|
||||
EC_KEY_METHOD_set_init(ec_method, NULL, openvpn_extkey_ec_finish, NULL, NULL, NULL, NULL);
|
||||
EC_KEY_METHOD_set_sign(ec_method, ecdsa_sign, ecdsa_sign_setup, ecdsa_sign_sig);
|
||||
|
||||
ec = EC_KEY_dup(EVP_PKEY_get0_EC_KEY(pkey));
|
||||
if (!ec)
|
||||
{
|
||||
EC_KEY_METHOD_free(ec_method);
|
||||
goto err;
|
||||
}
|
||||
if (!EC_KEY_set_method(ec, ec_method))
|
||||
{
|
||||
EC_KEY_METHOD_free(ec_method);
|
||||
goto err;
|
||||
}
|
||||
/* from this point ec_method will get freed when ec is freed */
|
||||
|
||||
privkey = EVP_PKEY_new();
|
||||
if (!EVP_PKEY_assign_EC_KEY(privkey, ec))
|
||||
{
|
||||
goto err;
|
||||
}
|
||||
/* from this point ec will get freed when privkey is freed */
|
||||
|
||||
if (!SSL_CTX_use_PrivateKey(ctx->ctx, privkey))
|
||||
{
|
||||
ec = NULL; /* avoid double freeing it below */
|
||||
goto err;
|
||||
}
|
||||
|
||||
EVP_PKEY_free(privkey); /* this will down ref privkey and ec */
|
||||
return 1;
|
||||
|
||||
err:
|
||||
/* Reach here only when ec and privkey can be independenly freed */
|
||||
if (privkey)
|
||||
{
|
||||
EVP_PKEY_free(privkey);
|
||||
}
|
||||
if(ec)
|
||||
{
|
||||
EC_KEY_free(ec);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif /* OPENSSL_VERSION_NUMBER > 1.1.0 dev */
|
||||
|
||||
int
|
||||
tls_ctx_use_external_private_key(struct tls_root_ctx *ctx,
|
||||
const char *cert_file, const char *cert_file_inline)
|
||||
@ -1183,18 +1300,33 @@ tls_ctx_use_external_private_key(struct tls_root_ctx *ctx,
|
||||
ASSERT(pkey); /* NULL before SSL_CTX_use_certificate() is called */
|
||||
X509_free(cert);
|
||||
|
||||
if (EVP_PKEY_get0_RSA(pkey))
|
||||
if (EVP_PKEY_id(pkey) == EVP_PKEY_RSA)
|
||||
{
|
||||
if (!tls_ctx_use_external_rsa_key(ctx, pkey))
|
||||
{
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
#if OPENSSL_VERSION_NUMBER > 0x10100000L && !defined(OPENSSL_NO_EC)
|
||||
else if (EVP_PKEY_id(pkey) == EVP_PKEY_EC)
|
||||
{
|
||||
if (!tls_ctx_use_external_ec_key(ctx, pkey))
|
||||
{
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
crypto_msg(M_WARN, "management-external-key requires a RSA certificate");
|
||||
crypto_msg(M_WARN, "management-external-key requires an RSA or EC certificate");
|
||||
goto err;
|
||||
}
|
||||
#else
|
||||
else
|
||||
{
|
||||
crypto_msg(M_WARN, "management-external-key requires an RSA certificate");
|
||||
goto err;
|
||||
}
|
||||
#endif /* OPENSSL_VERSION_NUMBER > 1.1.0 dev */
|
||||
return 1;
|
||||
|
||||
err:
|
||||
|
Loading…
Reference in New Issue
Block a user