Skip to content

Commit

Permalink
feat(x509.store) add set_purpose and verify_method parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
fffonion committed Apr 13, 2022
1 parent ddafe16 commit b7500fe
Show file tree
Hide file tree
Showing 6 changed files with 247 additions and 7 deletions.
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ Table of Contents
+ [store:add](#storeadd)
+ [store:load_file](#storeload_file)
+ [store:load_directory](#storeload_directory)
+ [store:set_purpose](#storeset_purpose)
+ [store:set_depth](#storeset_depth)
+ [store:verify](#storeverify)
* [resty.openssl.x509.revoked](#restyopensslx509revoked)
+ [revoked.new](#revokednew)
Expand Down Expand Up @@ -3763,9 +3765,41 @@ to explictly select provider to fetch algorithms.

[Back to TOC](#table-of-contents)

### store:set_purpose

**syntax**: *ok, err = store:set_purpose(purpose)*

Set the X509_STORE to match Key Usage and Extendend Key Usage when verifying the cert.
Possible values are:

```
sslclient SSL client
sslserver SSL server
nssslserver Netscape SSL server
smimesign S/MIME signing
smimeencrypt S/MIME encryption
crlsign CRL signing
any Any Purpose
ocsphelper OCSP helper
timestampsign Time Stamp signing
```

Normally user should use `verify_method` parameter of [store:verify](#storeverify) unless the purpose
is not included in the default verify methods.

[Back to TOC](#table-of-contents)

### store:set_depth

**syntax**: *ok, err = store:set_depth(depth)*

Set the verify depth.

[Back to TOC](#table-of-contents)

### store:verify

**syntax**: *chain, err = store:verify(x509, chain?, return_chain?, properties?)*
**syntax**: *chain, err = store:verify(x509, chain?, return_chain?, properties?, verify_method?)*

Verifies a X.509 object with the store. The first argument must be
[resty.openssl.x509](#restyopensslx509) instance. Optionally accept a validation chain as second
Expand All @@ -3778,6 +3812,11 @@ returns `true` only. If verification failed, returns `nil` and error explaining
Staring from OpenSSL 3.0, this functions accepts an optional `properties` parameter
to explictly select provider to fetch algorithms.

`verify_method` can be set to use predefined verify parameters such as `"default"`, `"pkcs7"`,
`"smime_sign"`, `"ssl_client"` and `"ssl_server"`. This set corresponding `purpose`, `trust` and
couple of other defaults but **does not** override the parameters set from
[store:set_purpose](#storeset_purpose).

[Back to TOC](#table-of-contents)

## resty.openssl.x509.revoked
Expand Down
1 change: 1 addition & 0 deletions lib/resty/openssl/include/ossl_typ.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ffi.cdef(
typedef struct X509_crl_st X509_CRL;
typedef struct x509_store_st X509_STORE;
typedef struct x509_store_ctx_st X509_STORE_CTX;
typedef struct x509_purpose_st X509_PURPOSE;
typedef struct v3_ext_ctx X509V3_CTX;
typedef struct asn1_string_st ASN1_INTEGER;
typedef struct asn1_string_st ASN1_ENUMERATED;
Expand Down
9 changes: 8 additions & 1 deletion lib/resty/openssl/include/x509_vfy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ ffi.cdef [[
int X509_STORE_load_locations(X509_STORE *ctx,
const char *file, const char *dir);
int X509_STORE_set_default_paths(X509_STORE *ctx);
int X509_STORE_set_flags(X509_STORE *ctx, unsigned long flags);
int X509_STORE_set_depth(X509_STORE *store, int depth);
int X509_STORE_set_purpose(X509_STORE *ctx, int purpose);

X509_STORE_CTX *X509_STORE_CTX_new(void);
void X509_STORE_CTX_free(X509_STORE_CTX *ctx);
Expand All @@ -31,7 +34,11 @@ ffi.cdef [[

int X509_STORE_CTX_get_error(X509_STORE_CTX *ctx);

int X509_STORE_set_flags(X509_STORE *ctx, unsigned long flags);
int X509_STORE_CTX_set_default(X509_STORE_CTX *ctx, const char *name);

int X509_PURPOSE_get_by_sname(char *sname);
X509_PURPOSE *X509_PURPOSE_get0(int idx);
int X509_PURPOSE_get_id(const X509_PURPOSE *xp);
]]

local _M = {}
Expand Down
42 changes: 41 additions & 1 deletion lib/resty/openssl/x509/store.lua
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,43 @@ function _M:load_directory(path, properties)
return true
end

function _M:verify(x509, chain, return_chain, properties)
function _M:set_depth(depth)
depth = depth and tonumber(depth)
if not depth then
return nil, "x509.store:set_depth: expect a number at #1"
end

if C.X509_STORE_set_depth(self.ctx, depth) ~= 1 then
return false, format_error("x509.store:set_depth")
end

return true
end

function _M:set_purpose(purpose)
if type(purpose) ~= "string" then
return nil, "x509.store:set_purpose: expect a string at #1"
end

local pchar = ffi.new("char[?]", #purpose, purpose)
local idx = C.X509_PURPOSE_get_by_sname(pchar)
idx = tonumber(idx)

if idx == -1 then
return false, "invalid purpose \"" .. purpose .. "\""
end

local purp = C.X509_PURPOSE_get0(idx)
local i = C.X509_PURPOSE_get_id(purp)

if C.X509_STORE_set_purpose(self.ctx, i) ~= 1 then
return false, format_error("x509.store:set_purpose: X509_STORE_set_purpose")
end

return true
end

function _M:verify(x509, chain, return_chain, properties, verify_method)
if not x509_lib.istype(x509) then
return nil, "x509.store:verify: expect a x509 instance at #1"
elseif chain and not chain_lib.istype(chain) then
Expand Down Expand Up @@ -148,6 +184,10 @@ function _M:verify(x509, chain, return_chain, properties)
return nil, format_error("x509.store:verify: X509_STORE_CTX_init")
end

if verify_method and C.X509_STORE_CTX_set_default(ctx, verify_method) ~= 1 then
return nil, "x509.store:verify: invalid verify_method \"" .. verify_method .. "\""
end

local code = C.X509_verify_cert(ctx)
if code == 1 then -- verified
if not return_chain then
Expand Down
48 changes: 44 additions & 4 deletions t/openssl/helper.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
local pkey = require "resty.openssl.pkey"
local x509 = require "resty.openssl.x509"
local name = require "resty.openssl.x509.name"
local extension = require "resty.openssl.x509.extension"
local bn = require "resty.openssl.bn"
local digest = require "resty.openssl.digest"
local BORINGSSL = require "resty.openssl.version".BORINGSSL

local function create_self_signed(key_opts, names)
local function create_self_signed(key_opts, names, is_ca, signing_key, issuing_name)
local key = pkey.new(key_opts or {
type = 'RSA',
bits = 1024,
Expand All @@ -20,18 +21,30 @@ local function create_self_signed(key_opts, names)
cert:set_not_after(now + 86400)

local nm = name.new()
for k, v in ipairs(names or {}) do
for k, v in pairs(names or {}) do
assert(nm:add(k, v))
end

assert(cert:set_subject_name(nm))
assert(cert:set_issuer_name(nm))
assert(cert:set_issuer_name(issuing_name or nm))

assert(cert:set_basic_constraints { CA = is_ca })
assert(cert:set_basic_constraints_critical(true))

if not is_ca then
assert(cert:add_extension(extension.new("extendedKeyUsage",
"serverAuth,clientAuth")))

assert(cert:add_extension(extension.new("subjectKeyIdentifier", "hash", {
subject = cert
})))
end

local dgst
if BORINGSSL then
dgst = digest.new("SHA256")
end
assert(cert:sign(key, dgst))
assert(cert:sign(signing_key or key, dgst))

-- make sure the private key is not included
cert = x509.new(cert:to_PEM())
Expand Down Expand Up @@ -122,10 +135,37 @@ local function encode_sorted_json(tbl)
return sort_json(require("cjson").encode(tbl))
end

local function create_cert_chain(depth, key_opts)
local last_key, last_cn
local certs, keys = {}, {}
for i=1, depth do
local cn, issuer
if last_key then
cn = "lua-resty-openssl Test Cert leaf " .. i - 1
issuer = name.new()
assert(issuer:add("CN", last_cn))
else
cn = "lua-resty-openssl Test Cert Root CA"
end
last_cn = cn

local crt, key = create_self_signed(key_opts,
{ CN = cn }, i < depth, last_key, issuer)

certs[i] = crt
keys[i] = key

last_key = key
end

return certs, keys
end


return {
create_self_signed = create_self_signed,
to_hex = to_hex,
myassert = myassert,
encode_sorted_json = encode_sorted_json,
create_cert_chain = create_cert_chain,
}
113 changes: 113 additions & 0 deletions t/openssl/x509/store.t
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,116 @@ B1BC968BD4F49D622AA89A81F2150152A41D829C
"
--- no_error_log
[error]
=== TEST 9: Set purpose
--- http_config eval: $::HttpConfig
--- config
location =/t {
content_by_lua_block {
local helper = require("helper")
local x509 = require("resty.openssl.x509")
local store = require("resty.openssl.x509.store")
local s = myassert(store.new())
local f = io.open("t/fixtures/GlobalSign.pem"):read("*a")
local c = myassert(x509.new(f))
local chain = myassert(s:add(c))
local f = io.open("t/fixtures/GlobalSign_sub.pem"):read("*a")
local c = myassert(x509.new(f))
myassert(s:set_purpose("sslclient"))
local ok, err = s:verify(c, nil, false)
ngx.say(ok, err)
myassert(s:set_purpose("crlsign"))
local ok, err = s:verify(c, nil, false)
ngx.say(ok, err)
}
}
--- request
GET /t
--- response_body eval
"nilunsupported certificate purpose
truenil
"
--- no_error_log
[error]
=== TEST 10: Set depth
--- http_config eval: $::HttpConfig
--- config
location =/t {
content_by_lua_block {
local helper = require "t.openssl.helper"
local store = require("resty.openssl.x509.store")
local chain = require("resty.openssl.x509.chain")
local certs, keys = helper.create_cert_chain(5, { type = 'EC', curve = "prime256v1" })
local s = myassert(store.new())
myassert(s:add(certs[1]))
local ch = chain.new()
for i=2, #certs-1 do
myassert(ch:add(certs[i]))
end
-- should be ok
ngx.say(s:verify(certs[#certs], ch))
-- in openssl < 1.1.0, depth are counted 1 more than later versions
-- we set it to be one less than enough to be prune to that case
myassert(s:set_depth(1))
-- openssl 1.0.2 will emit "unable to get local issuer certificate"
-- instead of "certificate chain too long"
ngx.say(s:verify(certs[#certs], ch))
}
}
--- request
GET /t
--- response_body_like eval
"truenil
nil(?:certificate chain too long|unable to get local issuer certificate)
"
--- no_error_log
[error]
=== TEST 11: Verify with verify_method
--- http_config eval: $::HttpConfig
--- config
location =/t {
content_by_lua_block {
local helper = require("helper")
local x509 = require("resty.openssl.x509")
local store = require("resty.openssl.x509.store")
local s = myassert(store.new())
local f = io.open("t/fixtures/GlobalSign.pem"):read("*a")
local c = myassert(x509.new(f))
local chain = myassert(s:add(c))
local f = io.open("t/fixtures/GlobalSign_sub.pem"):read("*a")
local c = myassert(x509.new(f))
local ok, err = s:verify(c, nil, false, nil, "ssl_client")
ngx.say(ok, err)
local ok, err = s:verify(c, nil, false, nil, "default")
ngx.say(ok, err)
myassert(s:set_purpose("sslclient"))
local ok, err = s:verify(c, nil, false, nil, "default")
ngx.say(ok, err)
}
}
--- request
GET /t
--- response_body eval
"nilunsupported certificate purpose
truenil
nilunsupported certificate purpose
"
--- no_error_log
[error]

0 comments on commit b7500fe

Please sign in to comment.