Software / code / prosody
Comparison
spec/util_crypto_spec.lua @ 12693:7c5afbdcbc77
util.crypto: New wrapper for some operations in OpenSSL's libcrypto
Specifically, ED25519 key generation/import/export, sign/verify operations,
and AES encrypt/decrypt.
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Fri, 24 Jun 2022 16:56:16 +0100 |
| child | 12700:899c057781cd |
comparison
equal
deleted
inserted
replaced
| 12692:b001b0f42512 | 12693:7c5afbdcbc77 |
|---|---|
| 1 local test_keys = { | |
| 2 ecdsa_private_pem = [[ | |
| 3 -----BEGIN PRIVATE KEY----- | |
| 4 MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg7taVK6bPtPz4ah32 | |
| 5 aD9CfvOah5omBxRVtzypwQXvZeahRANCAAQpKFeNIy27+lVo6bJslO6r2ty5rlb5 | |
| 6 xEiCx8GrrbJ8S7b5IPZCS7OrBaO2iqgOf7NMsgO12eLCfMZRnA+gCC34 | |
| 7 -----END PRIVATE KEY----- | |
| 8 ]]; | |
| 9 | |
| 10 ecdsa_public_pem = [[ | |
| 11 -----BEGIN PUBLIC KEY----- | |
| 12 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKShXjSMtu/pVaOmybJTuq9rcua5W | |
| 13 +cRIgsfBq62yfEu2+SD2QkuzqwWjtoqoDn+zTLIDtdniwnzGUZwPoAgt+A== | |
| 14 -----END PUBLIC KEY----- | |
| 15 ]]; | |
| 16 | |
| 17 eddsa_private_pem = [[ | |
| 18 -----BEGIN PRIVATE KEY----- | |
| 19 MC4CAQAwBQYDK2VwBCIEIOmrajEfnqdzdJzkJ4irQMCGbYRqrl0RlwPHIw+a5b7M | |
| 20 -----END PRIVATE KEY----- | |
| 21 ]]; | |
| 22 | |
| 23 eddsa_public_pem = [[ | |
| 24 -----BEGIN PUBLIC KEY----- | |
| 25 MCowBQYDK2VwAyEAFipbSXeGvPVK7eA4+hIOdutZTUUyXswVSbMGi0j1QKE= | |
| 26 -----END PUBLIC KEY----- | |
| 27 ]]; | |
| 28 | |
| 29 }; | |
| 30 | |
| 31 describe("util.crypto", function () | |
| 32 local crypto = require "util.crypto"; | |
| 33 local random = require "util.random"; | |
| 34 | |
| 35 describe("generate_ed25519_keypair", function () | |
| 36 local keypair = crypto.generate_ed25519_keypair(); | |
| 37 assert.is_not_nil(keypair); | |
| 38 assert.equal("ED25519", keypair:get_type()); | |
| 39 end) | |
| 40 | |
| 41 describe("import_private_pem", function () | |
| 42 it("can import ECDSA keys", function () | |
| 43 local ecdsa_key = crypto.import_private_pem(test_keys.ecdsa_private_pem); | |
| 44 assert.equal("id-ecPublicKey", ecdsa_key:get_type()); | |
| 45 end); | |
| 46 | |
| 47 it("can import EdDSA (Ed25519) keys", function () | |
| 48 local ed25519_key = crypto.import_private_pem(crypto.generate_ed25519_keypair():private_pem()); | |
| 49 assert.equal("ED25519", ed25519_key:get_type()); | |
| 50 end); | |
| 51 | |
| 52 it("can import RSA keys", function () | |
| 53 -- TODO | |
| 54 end); | |
| 55 | |
| 56 it("rejects invalid keys", function () | |
| 57 assert.is_nil(crypto.import_private_pem(test_keys.eddsa_public_pem)); | |
| 58 assert.is_nil(crypto.import_private_pem(test_keys.ecdsa_public_pem)); | |
| 59 assert.is_nil(crypto.import_private_pem("foo")); | |
| 60 assert.is_nil(crypto.import_private_pem("")); | |
| 61 end); | |
| 62 end); | |
| 63 | |
| 64 describe("import_public_pem", function () | |
| 65 it("can import ECDSA public keys", function () | |
| 66 local ecdsa_key = crypto.import_public_pem(test_keys.ecdsa_public_pem); | |
| 67 assert.equal("id-ecPublicKey", ecdsa_key:get_type()); | |
| 68 end); | |
| 69 | |
| 70 it("can import EdDSA (Ed25519) public keys", function () | |
| 71 local ed25519_key = crypto.import_public_pem(test_keys.eddsa_public_pem); | |
| 72 assert.equal("ED25519", ed25519_key:get_type()); | |
| 73 end); | |
| 74 | |
| 75 it("can import RSA public keys", function () | |
| 76 -- TODO | |
| 77 end); | |
| 78 end); | |
| 79 | |
| 80 describe("PEM export", function () | |
| 81 it("works", function () | |
| 82 local ecdsa_key = crypto.import_public_pem(test_keys.ecdsa_public_pem); | |
| 83 assert.equal("id-ecPublicKey", ecdsa_key:get_type()); | |
| 84 assert.equal(test_keys.ecdsa_public_pem, ecdsa_key:public_pem()); | |
| 85 | |
| 86 assert.has_error(function () | |
| 87 -- Fails because private key is not available | |
| 88 ecdsa_key:private_pem(); | |
| 89 end); | |
| 90 | |
| 91 local ecdsa_private_key = crypto.import_private_pem(test_keys.ecdsa_private_pem); | |
| 92 assert.equal(test_keys.ecdsa_private_pem, ecdsa_private_key:private_pem()); | |
| 93 end); | |
| 94 end); | |
| 95 | |
| 96 describe("sign/verify with", function () | |
| 97 local test_cases = { | |
| 98 ed25519 = { | |
| 99 crypto.ed25519_sign, crypto.ed25519_verify; | |
| 100 key = crypto.import_private_pem(test_keys.eddsa_private_pem); | |
| 101 sig_length = 64; | |
| 102 }; | |
| 103 ecdsa = { | |
| 104 crypto.ecdsa_sha256_sign, crypto.ecdsa_sha256_verify; | |
| 105 key = crypto.import_private_pem(test_keys.ecdsa_private_pem); | |
| 106 }; | |
| 107 }; | |
| 108 for test_name, test in pairs(test_cases) do | |
| 109 local key = test.key; | |
| 110 describe(test_name, function () | |
| 111 it("works", function () | |
| 112 local sign, verify = test[1], test[2]; | |
| 113 local sig = assert(sign(key, "Hello world")); | |
| 114 assert.is_string(sig); | |
| 115 if test.sig_length then | |
| 116 assert.equal(test.sig_length, #sig); | |
| 117 end | |
| 118 | |
| 119 do | |
| 120 local ok = verify(key, "Hello world", sig); | |
| 121 assert.is_truthy(ok); | |
| 122 end | |
| 123 do -- Incorrect signature | |
| 124 local ok = verify(key, "Hello world", sig:sub(1, -2)..string.char((sig:byte(-1)+1)%255)); | |
| 125 assert.is_falsy(ok); | |
| 126 end | |
| 127 do -- Incorrect message | |
| 128 local ok = verify(key, "Hello earth", sig); | |
| 129 assert.is_falsy(ok); | |
| 130 end | |
| 131 do -- Incorrect message (embedded NUL) | |
| 132 local ok = verify(key, "Hello world\0foo", sig); | |
| 133 assert.is_falsy(ok); | |
| 134 end | |
| 135 end); | |
| 136 end); | |
| 137 end | |
| 138 end); | |
| 139 | |
| 140 describe("ECDSA signatures", function () | |
| 141 local hex = require "util.hex"; | |
| 142 local sig = hex.decode((([[ | |
| 143 304402203e936e7b0bc62887e0e9d675afd08531a930384cfcf301 | |
| 144 f25d13053a2ebf141d02205a5a7c7b7ac5878d004cb79b17b39346 | |
| 145 6b0cd1043718ffc31c153b971d213a8e | |
| 146 ]]):gsub("%s+", ""))); | |
| 147 it("can be parsed", function () | |
| 148 local r, s = crypto.parse_ecdsa_signature(sig); | |
| 149 assert.is_string(r); | |
| 150 assert.is_string(s); | |
| 151 assert.equal(32, #r); | |
| 152 assert.equal(32, #s); | |
| 153 end); | |
| 154 it("fails to parse invalid signatures", function () | |
| 155 local invalid_sigs = { | |
| 156 ""; | |
| 157 "\000"; | |
| 158 string.rep("\000", 64); | |
| 159 string.rep("\000", 72); | |
| 160 string.rep("\000", 256); | |
| 161 string.rep("\255", 72); | |
| 162 string.rep("\255", 3); | |
| 163 }; | |
| 164 for _, sig in ipairs(invalid_sigs) do | |
| 165 local r, s = crypto.parse_ecdsa_signature(""); | |
| 166 assert.is_nil(r); | |
| 167 assert.is_nil(s); | |
| 168 end | |
| 169 | |
| 170 end); | |
| 171 it("can be built", function () | |
| 172 local r, s = crypto.parse_ecdsa_signature(sig); | |
| 173 local rebuilt_sig = crypto.build_ecdsa_signature(r, s); | |
| 174 assert.equal(sig, rebuilt_sig); | |
| 175 end); | |
| 176 end); | |
| 177 | |
| 178 describe("AES-GCM encryption", function () | |
| 179 it("works", function () | |
| 180 local message = "foo\0bar"; | |
| 181 local key_128_bit = random.bytes(16); | |
| 182 local key_256_bit = random.bytes(32); | |
| 183 local test_cases = { | |
| 184 { crypto.aes_128_gcm_encrypt, crypto.aes_128_gcm_decrypt, key = key_128_bit }; | |
| 185 { crypto.aes_256_gcm_encrypt, crypto.aes_256_gcm_decrypt, key = key_256_bit }; | |
| 186 }; | |
| 187 for _, params in pairs(test_cases) do | |
| 188 local iv = params.iv or random.bytes(12); | |
| 189 local encrypted = params[1](params.key, iv, message); | |
| 190 assert.not_equal(message, encrypted); | |
| 191 local decrypted = params[2](params.key, iv, encrypted); | |
| 192 assert.equal(message, decrypted); | |
| 193 end | |
| 194 end); | |
| 195 end); | |
| 196 end); |