Skip to content

Commit b3851f7

Browse files
Copilotzhaozg
andauthored
Implement Phase 2 roadmap tasks: EdDSA, X25519/X448, and ChaCha20-Poly1305 (#371)
* Implement Ed25519/Ed448 support with comprehensive tests (Task 2.1) Co-authored-by: zhaozg <[email protected]> * Implement X25519/X448 key exchange support (Task 2.2) Co-authored-by: zhaozg <[email protected]> * Add comprehensive ChaCha20-Poly1305 test suite (Task 2.3) Co-authored-by: zhaozg <[email protected]> * Add usage examples for Ed25519, X25519, and ChaCha20-Poly1305 to README Co-authored-by: zhaozg <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: zhaozg <[email protected]>
1 parent 22892fc commit b3851f7

File tree

5 files changed

+1032
-25
lines changed

5 files changed

+1032
-25
lines changed

README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,86 @@ assert(r==msg)
492492
print('Done')
493493
```
494494

495+
### Example 9: Ed25519 digital signatures
496+
497+
```lua
498+
local openssl = require('openssl')
499+
local pkey = openssl.pkey
500+
501+
-- Generate Ed25519 key pair
502+
local ctx = pkey.ctx_new("ED25519")
503+
local key = ctx:keygen()
504+
505+
-- Sign a message
506+
local message = "Hello, world!"
507+
local signature = pkey.sign(key, message)
508+
509+
-- Verify the signature
510+
local verified = pkey.verify(key, message, signature)
511+
print("Signature verified:", verified) -- true
512+
513+
-- Export and import keys
514+
local pem = key:export("pem")
515+
local imported_key = pkey.read(pem, true, "pem")
516+
print("Key successfully imported")
517+
```
518+
519+
### Example 10: X25519 key exchange
520+
521+
```lua
522+
local openssl = require('openssl')
523+
local pkey = openssl.pkey
524+
525+
-- Alice and Bob each generate a key pair
526+
local alice = pkey.ctx_new("X25519"):keygen()
527+
local bob = pkey.ctx_new("X25519"):keygen()
528+
529+
-- Alice derives shared secret using Bob's public key
530+
local alice_secret = alice:derive(bob)
531+
532+
-- Bob derives shared secret using Alice's public key
533+
local bob_secret = bob:derive(alice)
534+
535+
-- Both secrets should be identical
536+
assert(alice_secret == bob_secret)
537+
print("Shared secret established:", openssl.hex(alice_secret))
538+
```
539+
540+
### Example 11: ChaCha20-Poly1305 AEAD encryption
541+
542+
```lua
543+
local openssl = require('openssl')
544+
local cipher = openssl.cipher
545+
546+
-- Get ChaCha20-Poly1305 cipher
547+
local cc20 = cipher.get("chacha20-poly1305")
548+
549+
-- Prepare key and nonce
550+
local key = string.rep("k", 32) -- 256-bit key
551+
local nonce = string.rep("n", 12) -- 96-bit nonce
552+
local message = "Secret message"
553+
local aad = "Additional authenticated data"
554+
555+
-- Encrypt with AAD
556+
local enc = cc20:encrypt_new()
557+
enc:ctrl(openssl.cipher.EVP_CTRL_GCM_SET_IVLEN, #nonce)
558+
enc:init(key, nonce)
559+
enc:update(aad, true) -- Set AAD
560+
local ciphertext = enc:update(message) .. enc:final()
561+
local tag = enc:ctrl(openssl.cipher.EVP_CTRL_GCM_GET_TAG, 16)
562+
563+
-- Decrypt with AAD
564+
local dec = cc20:decrypt_new()
565+
dec:ctrl(openssl.cipher.EVP_CTRL_GCM_SET_IVLEN, #nonce)
566+
dec:init(key, nonce)
567+
dec:ctrl(openssl.cipher.EVP_CTRL_GCM_SET_TAG, tag)
568+
dec:update(aad, true) -- Set AAD
569+
local plaintext = dec:update(ciphertext) .. dec:final()
570+
571+
assert(plaintext == message)
572+
print("Decrypted:", plaintext)
573+
```
574+
495575
For more examples, please see test lua script file.
496576

497577
---

src/pkey.c

Lines changed: 134 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1562,44 +1562,86 @@ static int openssl_derive(lua_State *L)
15621562
/* OpenSSL 3.0+ way: use PARAM API compatible check */
15631563
luaL_argcheck(L,
15641564
(ptype == EVP_PKEY_DH && pkey_is_type(pkey, EVP_PKEY_DH))
1565-
|| (ptype == EVP_PKEY_EC && pkey_is_type(pkey, EVP_PKEY_EC)),
1565+
|| (ptype == EVP_PKEY_EC && pkey_is_type(pkey, EVP_PKEY_EC))
1566+
#ifdef EVP_PKEY_X25519
1567+
|| ptype == EVP_PKEY_X25519
1568+
#ifdef EVP_PKEY_X448
1569+
|| ptype == EVP_PKEY_X448
1570+
#endif
1571+
#endif
1572+
,
15661573
1,
1567-
"only support DH or EC private key");
1574+
"only support DH, EC, X25519 or X448 private key");
15681575
#else
15691576
/* OpenSSL 1.x way */
15701577
luaL_argcheck(L,
15711578
(ptype == EVP_PKEY_DH && EVP_PKEY_get0_DH(pkey) != NULL)
1572-
|| (ptype == EVP_PKEY_EC && EVP_PKEY_get0_EC_KEY(pkey) != NULL),
1579+
|| (ptype == EVP_PKEY_EC && EVP_PKEY_get0_EC_KEY(pkey) != NULL)
1580+
#ifdef EVP_PKEY_X25519
1581+
|| ptype == EVP_PKEY_X25519
1582+
#ifdef EVP_PKEY_X448
1583+
|| ptype == EVP_PKEY_X448
1584+
#endif
1585+
#endif
1586+
,
15731587
1,
1574-
"only support DH or EC private key");
1588+
"only support DH, EC, X25519 or X448 private key");
15751589
#endif
15761590
#elif !defined(OPENSSL_NO_DH)
15771591
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
15781592
/* OpenSSL 3.0+ way: use PARAM API compatible check */
15791593
luaL_argcheck(L,
1580-
ptype == EVP_PKEY_DH && pkey_is_type(pkey, EVP_PKEY_DH),
1594+
(ptype == EVP_PKEY_DH && pkey_is_type(pkey, EVP_PKEY_DH))
1595+
#ifdef EVP_PKEY_X25519
1596+
|| ptype == EVP_PKEY_X25519
1597+
#ifdef EVP_PKEY_X448
1598+
|| ptype == EVP_PKEY_X448
1599+
#endif
1600+
#endif
1601+
,
15811602
1,
1582-
"only support DH or EC private key");
1603+
"only support DH, X25519 or X448 private key");
15831604
#else
15841605
/* OpenSSL 1.x way */
15851606
luaL_argcheck(L,
1586-
ptype == EVP_PKEY_DH && EVP_PKEY_get0_DH(pkey) != NULL,
1607+
(ptype == EVP_PKEY_DH && EVP_PKEY_get0_DH(pkey) != NULL)
1608+
#ifdef EVP_PKEY_X25519
1609+
|| ptype == EVP_PKEY_X25519
1610+
#ifdef EVP_PKEY_X448
1611+
|| ptype == EVP_PKEY_X448
1612+
#endif
1613+
#endif
1614+
,
15871615
1,
1588-
"only support DH or EC private key");
1616+
"only support DH, X25519 or X448 private key");
15891617
#endif
15901618
#elif !defined(OPENSSL_NO_EC)
15911619
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
15921620
/* OpenSSL 3.0+ way: use PARAM API compatible check */
15931621
luaL_argcheck(L,
1594-
ptype == EVP_PKEY_EC && pkey_is_type(pkey, EVP_PKEY_EC),
1622+
(ptype == EVP_PKEY_EC && pkey_is_type(pkey, EVP_PKEY_EC))
1623+
#ifdef EVP_PKEY_X25519
1624+
|| ptype == EVP_PKEY_X25519
1625+
#ifdef EVP_PKEY_X448
1626+
|| ptype == EVP_PKEY_X448
1627+
#endif
1628+
#endif
1629+
,
15951630
1,
1596-
"only support DH or EC private key");
1631+
"only support EC, X25519 or X448 private key");
15971632
#else
15981633
/* OpenSSL 1.x way */
15991634
luaL_argcheck(L,
1600-
ptype == EVP_PKEY_EC && EVP_PKEY_get0_EC_KEY(pkey) != NULL,
1635+
(ptype == EVP_PKEY_EC && EVP_PKEY_get0_EC_KEY(pkey) != NULL)
1636+
#ifdef EVP_PKEY_X25519
1637+
|| ptype == EVP_PKEY_X25519
1638+
#ifdef EVP_PKEY_X448
1639+
|| ptype == EVP_PKEY_X448
1640+
#endif
1641+
#endif
1642+
,
16011643
1,
1602-
"only support DH or EC private key");
1644+
"only support EC, X25519 or X448 private key");
16031645
#endif
16041646
#endif
16051647

@@ -1663,9 +1705,27 @@ static int openssl_sign(lua_State *L)
16631705
if (is_SM2) md_alg = "sm3";
16641706
#endif
16651707

1708+
/* For EdDSA keys (Ed25519, Ed448), allow NULL digest as they use
1709+
* internal hash functions. Detect EdDSA and allow omitting or explicitly passing nil. */
1710+
#ifdef EVP_PKEY_ED25519
1711+
{
1712+
int pkey_type = EVP_PKEY_type(EVP_PKEY_id(pkey));
1713+
if (pkey_type == EVP_PKEY_ED25519
1714+
#ifdef EVP_PKEY_ED448
1715+
|| pkey_type == EVP_PKEY_ED448
1716+
#endif
1717+
) {
1718+
/* EdDSA keys don't need a digest - use NULL */
1719+
md = NULL;
1720+
} else {
1721+
md = get_digest(L, 3, md_alg);
1722+
}
1723+
}
1724+
#else
16661725
md = get_digest(L, 3, md_alg);
1726+
#endif
16671727
#if defined(OPENSSL_SUPPORT_SM2)
1668-
if (is_SM2) is_SM2 = EVP_MD_type(md) == NID_sm3;
1728+
if (is_SM2 && md) is_SM2 = EVP_MD_type(md) == NID_sm3;
16691729
#endif
16701730

16711731
ctx = EVP_MD_CTX_new();
@@ -1682,20 +1742,38 @@ static int openssl_sign(lua_State *L)
16821742

16831743
ret = EVP_DigestSignInit(ctx, NULL, md, NULL, pkey);
16841744
if (ret == 1) {
1685-
ret = EVP_DigestSignUpdate(ctx, data, data_len);
1686-
if (ret == 1) {
1687-
size_t siglen = 0;
1688-
unsigned char *sigbuf = NULL;
1689-
ret = EVP_DigestSignFinal(ctx, NULL, &siglen);
1745+
#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
1746+
/* For EdDSA (and other algorithms), use one-shot API if available (OpenSSL >= 1.1.1) */
1747+
if (md == NULL) {
1748+
size_t siglen = 0;
1749+
ret = EVP_DigestSign(ctx, NULL, &siglen, (const unsigned char *)data, data_len);
16901750
if (ret == 1) {
1691-
siglen += 2;
1692-
sigbuf = OPENSSL_malloc(siglen);
1693-
ret = EVP_DigestSignFinal(ctx, sigbuf, &siglen);
1751+
unsigned char *sigbuf = OPENSSL_malloc(siglen);
1752+
ret = EVP_DigestSign(ctx, sigbuf, &siglen, (const unsigned char *)data, data_len);
16941753
if (ret == 1) {
16951754
lua_pushlstring(L, (char *)sigbuf, siglen);
16961755
}
16971756
OPENSSL_free(sigbuf);
16981757
}
1758+
} else
1759+
#endif
1760+
{
1761+
/* Traditional three-step approach for algorithms with digest */
1762+
ret = EVP_DigestSignUpdate(ctx, data, data_len);
1763+
if (ret == 1) {
1764+
size_t siglen = 0;
1765+
unsigned char *sigbuf = NULL;
1766+
ret = EVP_DigestSignFinal(ctx, NULL, &siglen);
1767+
if (ret == 1) {
1768+
siglen += 2;
1769+
sigbuf = OPENSSL_malloc(siglen);
1770+
ret = EVP_DigestSignFinal(ctx, sigbuf, &siglen);
1771+
if (ret == 1) {
1772+
lua_pushlstring(L, (char *)sigbuf, siglen);
1773+
}
1774+
OPENSSL_free(sigbuf);
1775+
}
1776+
}
16991777
}
17001778
}
17011779

@@ -1741,7 +1819,25 @@ static int openssl_verify(lua_State *L)
17411819
if (is_SM2) md_alg = "sm3";
17421820
#endif
17431821

1822+
/* For EdDSA keys (Ed25519, Ed448), allow NULL digest as they use
1823+
* internal hash functions. Detect EdDSA and allow omitting or explicitly passing nil. */
1824+
#ifdef EVP_PKEY_ED25519
1825+
{
1826+
int pkey_type = EVP_PKEY_type(EVP_PKEY_id(pkey));
1827+
if (pkey_type == EVP_PKEY_ED25519
1828+
#ifdef EVP_PKEY_ED448
1829+
|| pkey_type == EVP_PKEY_ED448
1830+
#endif
1831+
) {
1832+
/* EdDSA keys don't need a digest - use NULL */
1833+
md = NULL;
1834+
} else {
1835+
md = get_digest(L, 4, md_alg);
1836+
}
1837+
}
1838+
#else
17441839
md = get_digest(L, 4, md_alg);
1840+
#endif
17451841

17461842
ctx = EVP_MD_CTX_new();
17471843
#if defined(OPENSSL_SUPPORT_SM2)
@@ -1758,11 +1854,24 @@ static int openssl_verify(lua_State *L)
17581854

17591855
ret = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey);
17601856
if (ret == 1) {
1761-
ret = EVP_DigestVerifyUpdate(ctx, data, data_len);
1762-
if (ret == 1) {
1763-
ret = EVP_DigestVerifyFinal(ctx, (unsigned char *)signature, signature_len);
1857+
#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
1858+
/* For EdDSA (and other algorithms), use one-shot API if available (OpenSSL >= 1.1.1) */
1859+
if (md == NULL) {
1860+
ret = EVP_DigestVerify(ctx, (const unsigned char *)signature, signature_len,
1861+
(const unsigned char *)data, data_len);
17641862
if (ret == 1) {
1765-
lua_pushboolean(L, ret == 1);
1863+
lua_pushboolean(L, 1);
1864+
}
1865+
} else
1866+
#endif
1867+
{
1868+
/* Traditional three-step approach for algorithms with digest */
1869+
ret = EVP_DigestVerifyUpdate(ctx, data, data_len);
1870+
if (ret == 1) {
1871+
ret = EVP_DigestVerifyFinal(ctx, (unsigned char *)signature, signature_len);
1872+
if (ret == 1) {
1873+
lua_pushboolean(L, ret == 1);
1874+
}
17661875
}
17671876
}
17681877
}

0 commit comments

Comments
 (0)