1.0 Language
Lua plugins are run using the standard Lua 5.3 interpreter.
Plugins may use the following standard Lua 5.3 symbols:
assert
bit32
collectgarbage
coroutine
error
getmetatable
ipairs
load
math
next
pairs
rawequal
rawget
rawlen
rawset
select
setmetatable
string
table
tonumber
tostring
type
utf8
xpcall
_VERSION
_G
The following will be supported soon:
print
require
Support is not planned for the following:
loadfile
arg
debug
dofile
package
os
io
The following Fortanix-Data-Security-Manager (DSM)-defined symbols are also in scope, detailed below:
cbor
json
Error
Blob
EntityId
Sobject
App
User
Plugin
Group
AuditLog
principal
this_plugin
digest
null
BigNum
Time
EcGroup
EcPoint
require_approval_for
2.0 Invoking Plugins
2.1 The run Function
A plugin must define a function run.
When the plugin is invoked, run will be called with the following parameters:
input: The JSON data with which the plugin was invoked, decoded into a Lua object as injson.decode, ornilif no input data was supplied.url: The URL that was used to invoke the plugin, represented as a Lua table:url.path: A Lua array of path components, for example,['sys', 'v1', 'plugins', '08ce8c3e-50fc-4c95-a633-ca417e6f2828'].
Users and apps may append arbitrary custom path components when invoking a plugin.url.query: The query string (the part of the URL after?), ornilif the URL does not include a query string.
method: The method used to invoke the plugin, either'POST'or'GET'.
The function run may accept any prefix of these arguments, for example:
function run() ... endfunction run(input) ... endfunction run(input, url, method) ... end
The function run may return the following:
nil: the plugin invocation returns successfully with no content.an encodable Lua object: the object is encoded to JSON as in
json.encodeand returned.niland anError: the plugin invocation errors as described in theErrorsection below.
2.2 The check Function
A plugin can optionally define a function check.
If this function is defined, it will be called before function run and with the same input parameters that will be passed to function run, for example:function check(input, url, method) ... end.
If the check function returns an error or throws an exception, the plugin
invocation is aborted and the error will be returned to the caller.
You can use function check for access checks, input validation logic, and so on.
You can also call require_approval_for in function check to require the caller to submit an approval request to invoke the plugin subject to the approval policy defined on the object you pass to require_approval_for. For example:
function check(input)
-- Validate input fields
if not input.signing_key_id then return nil, 'missing field `signing_key_id`' end
if not input.hash then return nil, 'missing field `hash`' end
-- Look up the signing key
local signing_key = assert(Sobject { kid = input.signing_key_id })
-- If the signing key is subject to an approval policy, then the following
-- call will ensure that this plugin can only be invoked by creating an
-- approval request. Otherwise the call has no effect.
require_approval_for(signing_key)
end
function run(input)
local signing_key = assert(Sobject { kid = input.signing_key_id })
local signature =assert(signing_key:sign { hash = input.hash, hash_alg = 'SHA256' })
return signature
endNote that even though you can call require_approval_for in function run, it will not have any effect. You should only use it in function check.
2.3 The Blob Class
A Blob represents arbitrary binary data.
DSM REST APIs use JSON and distinguish between
UTF-8 strings (e.g. key name), which use JSON strings, and
binary data (e.g. key material), which use base64-encoded JSON strings.
JSON strings must be valid UTF-8, so in general raw binary data is not a valid JSON string.
Lua plugin APIs use Lua strings for UTF-8 strings and Blobs for binary data.
For interoperability with the JSON REST APIs, a base64-encoded Lua string may be used instead of a Blob where binary data is expected.
Constructors
Blob.from_bytes(string)Constructs a
Blobfrom a Lua string with raw binary data.Note that Lua strings do not need to be valid UTF-8.
For example:
local blob = Blob.from_bytes('\1\2\255').Blob.from_base64(string)Constructs a
Blobfrom base64-encoded data in a Lua string.For example:
local blob = Blob.from_base64('AQL/').Blob.from_hex(string)Constructs a
Blobfrom hex-encoded data in a Lua string.For example:
local blob = Blob.from_hex('0102FF').Blob.from_base58(string)Constructs a
Blobfrom base58-encoded data in a Lua string.For example:
local blob = Blob.from_base58('LiA').Blob.from_base58_check(string)Constructs a
Blobfrom base58-encoded data in a Lua string. Unlike plain base58, this also includes a SHA-256d checksum.For example:
local blob = Blob.from_base58_check('3DyWSpcNi').Blob.random(num_bytes)Constructs a random
Blobwith the given number of bytes.The bytes will be generated using DSM's cryptographically secure random number generator.
Blob.random { bytes = num_bytes }andBlob.random { bits = num_bits }are also supported.num_bytesmust be a whole number;num_bitsmust be a multiple of 8.Blob.pack(fmt, ...)This is equivalent to
Blob.from_bytes(string.pack(fmt, ...)).Refer to the documentation for
string.pack.For example:
assert(Blob.pack('>i8', 0xDEADBEEF):hex() == '00000000DEADBEEF')
Methods
blob:unpack(fmt)This is equivalent to
string.unpack(fmt, blob:bytes()).Refer to the documentation for
string.unpack.For example:
assert(Blob.from_hex('00000000DEADBEEF'):unpack('>i8') == 0xDEADBEEF)blob:bytes()Returns a Lua string with the raw binary data.
For example:
assert(blob:bytes() == '\1\2\255')blob:base64()Returns a Lua string with the base64-encoded binary data.
For example:
assert(blob:base64() == 'AQL/')blob:hex()Returns a Lua string with the hex-encoded binary data.
For example:
assert(blob:hex() == '0102FF')blob:base58()Returns a Lua string with the base58-encoded binary data.
E.g.:
assert(blob:base58() == 'LiA')blob:base58_check()Returns a Lua string with the base58-encoding (including SHA-256d checksum) of the binary data.
For example:
assert(blob:base58_check() == '3DyWSpcNi')blob:slice(lo_byte, hi_byte)Returns a blob consisting of the bytes between
lo_byteandhi_byte, 1-indexed and inclusive.For example,
Blob.from_bytes('\1\2\3\4'):slice(2, 3) == Blob.from_bytes('\2\3').blob .. blob2..may be used to concatenate two blobs, e.g.Blob.from_bytes('\1') .. Blob.from_bytes('\2') == Blob.from_bytes('\1\2').#blob#may be used to get the number of bytes in the blob, e.g.assert(#Blob.from_bytes('\1\1\1') == 3).blob & blob2,blob | blob2,blob ~ blob2&,|, and~may be used to take the bitwise and, the bitwise or, and the bitwise xor of two blobs. The blobs must be the same size, or else these operations will throw an exception.~blob~may be used to take the bitwise not of a blob.
2.4 The cbor Module
cbor.encode(object)cbor.encodeencodes (serializes) a Lua object into aBlobcontaining binary data in the CBOR format.Encodable Lua objects include:
nil, which encodes to null.Lua numbers and booleans, which encode to CBOR numbers and booleans.
Blobs, which encode to CBOR byte strings.Lua strings, which encode to CBOR text strings.
CBOR text string should be valid UTF-8, while Lua strings may contain arbitrary binary data. To encode a Lua string representing potentially non-UTF-8 binary data, use
Blob.from_bytes(string).
Lua tables, which encode to CBOR maps or CBOR array.
A table is encoded as an array if the set of keys is
{1, 2, .., n}for somen.
A Lua object may use a custom encoder by adding an encoder function
__tocborto the metatable.There is no corresponding way to use a custom decoder.
For example,
null = setmetatable({}, { __tocbor = function(self) return cbor.encode(nil) end })cbor.decode(data)cbor.decodedecodes (deserializes) a blob containing CBOR binary data, returning a Lua object that encodes to the given CBOR as defined incbor.encode.CBOR byte strings decode to
Blobs and CBOR nil decodes tonull.The other CBOR types decode to primitive strings, numbers, booleans, and tables.
2.5 The json Module
json.encode(object)json.encodeencodes (serializes) a Lua object into Lua string containing JSON data.json.encode(object)returns the JSON corresponding tocbor.encode(object).CBOR byte strings (
Blobs) become base64-encoded JSON strings.json.decode(data)json.decodedecodes (deserializes) a Lua string containing JSON data, returning a Lua object that encodes to the given JSON, as defined injson.encode.JSON nil decodes to
null. The other JSON types decode to primitive strings, numbers, booleans, and tables.JSON strings always decode to Lua strings, including base64-encoded JSON strings.
For example, encoding a
Blobto JSON and then decoding the JSON will produce a base64-encoded Lua string. To get the originalBlob, applyBlob.from_base64to the decoded string.
2.6 The null Object
This is an object that encodes to CBOR or JSON null.
This is returned on success in methods like sobject:delete() that return no data.
We do not return nil here so that assert(sobject:delete()) works.
2.7 The Array Class
An Array is a table that always serializes to an JSON/CBOR array, not a map.
Non-numeric keys in an Array (more precisely, keys not included in ipairs) are ignored during serialization.
Motivation
The empty map {} and the empty array [] in JSON both deserialize to the empty Lua table {}.
The empty Lua table serializes to the empty map. Array() serializes to the empty array.
Constructors
Array() or Array {} is the empty array.
Array(table) returns setmetatable(table, Array).
E.g. assert(json.encode(Array { 1, 2, a = 3 }) == '[1,2]').
2.8 The Error Class
This class is used for errors from the plugin APIs.
If an error occurs, a function will return two values: nil and the error (this is a standard Lua idiom).
An error has two properties: an HTTP status code number status and message string.
Custom errors may be constructed using Error.new { status = , message = "" }.
For example,
function run()
local result, error = Sobject { name = "nonexistent key" }
assert(result == nil)
assert(error.status == 404)
// return a custom 401 Unauthorized error to the plugin invoker
return nil, Error.new { status = 401, message = "custom error" }
end2.9 The Function principal
principal() returns the EntityId of the entity that invoked the plugin.
For example,
function run()
if principal():type() ~= 'user'
return nil, Error.new { = 401, message = "Plugin may only be invoked by users" }
end
end2.10 The Function this_plugin
this_plugin() returns a Plugin object corresponding to the plugin being invoked.
For example,
function run()
return "the name of this plugin is " .. this_plugin().name
end2.11 The AuditLog Module
AuditLog.log { message = message, severity = severity }Write an audit log entry associated with this plugin with the given message and severity.
severitymay be one of'INFO','WARNING','ERROR', or'CRITICAL'.AuditLog.get_all { ... }Get audit log entries matching the requested filters. This corresponds to
GET /sys/v1/logsin the REST API.Returns an array of audit log entries, or
niland anErrorif the logs could not be fetched.
2.12 The Sobject Class
This represents a security object, transient or persisted.
The properties of this object are described in the REST API docs (it is named KeyObject there). sobject.value and sobject.pub_key are Blobs (unless they are nil).
sobject.creator is an EntityId object.
Like apps, plugin may create transient keys by adding transient = true to the create, import, unwrap, derive, or agree request.
These transient keys only exist during the invocation of the plugin. As soon as the plugin's function run returns, they become invalid.
Constructors
Sobject { id = '<uuid>' }orSobject { kid = '<uuid>' }This returns the persisted security object with the given UUID, or
niland anErrorif no such object exists.For example,
local sobject = assert(Sobject { id = '123e4567-e89b-12d3-a456-426655440000' })Sobject { name = 'key name' }This returns the persisted security object with the given name, or
niland anErrorif no such object exists.Sobject.create { ... }Create a security object. This corresponds to
POST /crypto/v1/keysin the REST API.The arguments to this function are described in the REST API documentation for
SobjectRequest.This returns the created
Sobject, orniland anErrorif the object could not be created.For example,
local sobject = assert(Sobject.create { name = "my key", obj_type = "AES", key_size = 128 })Sobject.import { ... }Import a security object. This corresponds to
PUT /crypto/v1/keysin the REST API.The arguments to this function are described in the REST API documentation for
SobjectRequest.This returns the import
Sobject, orniland anErrorif the object could not be imported.For example,
local sobject = assert(Sobject.import { name = "my key", obj_type = "AES", value = Blob.random { bits = 128 } })Sobject.get_all { ... }Get detailed information on all security objects. This corresponds to
GET /crypto/v1/keysin the REST API.This returns
Sobject, orniland anError.For example,
local sobject, error = Sobject.get_all { }Sobject.get_by_names { ... }Get detailed information on security objects that match names.
This returns
Sobject, orniland anError.For example,
local sobject, error = Sobject.get_by_names { "My key", "Another key", "Unavailable key" }
Methods
sobject:update { ... }Update the properties of
sobject. This corresponds toPATCH /crypto/v1/keys/in the REST API.The arguments to this method are described in the REST API documentation for
SobjectRequest.This method may not be called on a transient
sobject.Returns
nullon success, orniland anErrorif the object could not be updated.For example,
assert(sobject:update { name = "new key name" }) -- throw an exception if update failssobject:delete()Delete the
sobject. This corresponds toDELETE /crypto/v1/keys/in the REST API.This method may not be called on a transient
sobject.Returns
nullon success, orniland anErrorif the object could not be deleted.For example,
assert(sobject:delete()) -- throw an exception if update failssobject:export()Retrieve the value of
sobject. This corresponds toGET /crypto/v1/keys/exportin the REST API.This returns a
Sobjectwhich has avalueproperty on success, or returnsniland anErrorif the object could not be exported.For example,
local exported_value = assert(Sobject { name = 'my key' }):export().valuesobject:descriptor()Returns
{ kid = "<uuid>" }ifsobjectis persisted, or{ transient_key = blob }ifsobjectis transient.sobject:encrypt { ... }Encrypt data using
sobject. This corresponds toPOST /crypto/v1/encryptin the REST API.The arguments to this method are described in the REST API docs for
EncryptRequest.Returns an object corresponding to
EncryptResponsein the REST API on success, orniland anErrorif the data could not be encrypted.In the returned object,
cipherandivareBlobs.The following Lua plugin demonstrates the encrypt operation using a symmetric key (AES). However, you can also perform the operation with an asymmetric key, such as RSA or EC.
For example,
Plugin input:
plugin_input: { "key_name" : "aes01Test", "plain_text" : "hello world", "mode" : "CBC" }Plugin code:
function run(input) local sobject = assert(Sobject { name = input.key_name }) local encrypt_response = assert(sobject:encrypt { plain = Blob.from_bytes(input.plain_text), mode = input.mode }) return { cipher = encrypt_response.cipher:base64(), iv = encrypt_response.iv:base64() } endPlugin output:
{ "cipher": "b/GWYnVVmrGxqPBenGCrlA==", "iv": "XoYrAjkoke9bf9/A1/Cq4A==" }sobject:decrypt { ... }Decrypt data using
sobject. This corresponds tothe POST /crypto/v1/decryptendpoint in the REST API.The arguments to this method are described in the REST API docs for
DecryptRequest.Returns an object corresponding to
DecryptResponsein the REST API on success, orniland anErrorif the data could not be decrypted.In the returned object,
plainis aBlob.The following Lua plugin demonstrates the decrypt operation using a symmetric key (AES). However, you can also perform the operation with an asymmetric key, such as RSA or EC.
For example:
Plugin input:
plugin_input: { "key_name" : "aes01Test", "cipher" : "b/GWYnVVmrGxqPBenGCrlA==", "mode" : "CBC", "iv" : "XoYrAjkoke9bf9/A1/Cq4A==" }Plugin code:
function run(input) local sobject = assert(Sobject { name = input.key_name }) local encrypt_response = assert(sobject:encrypt { plain = Blob.from_bytes(input.plain_text), mode = input.mode }) return { cipher = encrypt_response.cipher:base64(), iv = encrypt_response.iv:base64() } endPlugin output:
[ "hello world" ]sobject:batch_encrypt { ... }andsobject:batch_decrypt { ... }batch_encrypt: Use this method to perform batch encrypt with one or more keys. The order of batch items in the response matches that of the request, and an individual status code is returned for each batch item. This corresponds to thePOST /crypto/v1/keys/batch/encryptendpoint in the REST API.batch_decrypt: Use this method to perform batch decrypt with one or more keys. The order of batch items in the response matches that of the request, and an individual status code is returned for each batch item. This corresponds to thePOST /crypto/v1/keys/batch/decryptendpoint in the REST API.The following Lua plugin demonstrates multiple encrypt and decrypt operations using a single AES key (though multiple keys are also supported). These operations are performed as single
batch-encryptandbatch-decryptrequests, respectively.For example:
Plugin input:
plugin_input: { "key_name" : "aes01Test", "alg" : "AES", "mode" : "CBC", "plain_text1" : "hello world", "plain_text2" : "loren ipsum" }Plugin code:
function run(input) local sobject = assert(Sobject { name = input.key_name }) -- perform batch encrypt for the input plain_texts local plain1 = input.plain_text1 local enc_body1 = { alg = input.alg, plain = Blob.from_bytes(plain1), mode = input.mode } local enc_req1 = { kid = sobject.kid, request = enc_body1 } local plain2 = input.plain_text2 local enc_body2 = { alg = input.alg, plain = Blob.from_bytes(plain2), mode = input.mode } local enc_req2 = { kid = sobject.kid, request = enc_body2 } local batch_enc_req = {} table.insert(batch_enc_req, enc_req1) table.insert(batch_enc_req, enc_req2) local batch_enc_response, err = batch_encrypt(batch_enc_req) -- check batch encrypt request is successful assert(err == nil) assert(batch_enc_response[1]["status"] == 200) assert(batch_enc_response[2]["status"] == 200) -- perform batch decrypt for the cipher_texts obtained above local cipher1 = batch_enc_response[1]["body"]["cipher"] local iv1 = batch_enc_response[1]["body"]["iv"] local dec_body1 = { alg = input.alg, mode = input.mode, cipher = cipher1, iv = iv1 } local decr_req1 = { kid = sobject.kid, request = dec_body1 } local cipher2 = batch_enc_response[2]["body"]["cipher"] local iv2 = batch_enc_response[2]["body"]["iv"] local dec_body2 = { alg = input.alg, mode = input.mode, cipher = cipher2, iv = iv2 } local decr_req2 = { kid = sobject.kid, request = dec_body2 } local batch_decr_req = {} table.insert(batch_decr_req, decr_req1) table.insert(batch_decr_req, decr_req2) local batch_decr_response, err = batch_decrypt(batch_decr_req) -- check batch decrypt request is successful assert(err == nil) assert(batch_decr_response[1]["status"] == 200) assert(batch_decr_response[2]["status"] == 200) -- check decrypted cipher match their corresponding input plain_texts assert(plain1 == batch_decr_response[1]["body"]["plain"]:bytes()) assert(plain2 == batch_decr_response[2]["body"]["plain"]:bytes()) -- return the decrypted texts return { batch_decr_response[1]["body"]["plain"]:bytes(), batch_decr_response[2]["body"]["plain"]:bytes() } endPlugin output:
[ "hello world", "loren ipsum" ]sobject:sign { ... }Sign data using
sobject. This corresponds toPOST /crypto/v1/signin the REST API.The arguments to this method are described in the REST API docs for
SignRequest.Returns a object corresponding to
SignResponsein the REST API on success, orniland anErrorif the data could not be signed.In the returned object,
signatureis aBlob.For example,
local sobject = assert(Sobject { name = "my rsa key" }) local sign_response = assert(sobject:sign { data = Blob.from_bytes("hello world"), hash_alg = 'SHA256' }) return sign_response.signaturesobject:verify { ... }Verify data signed by
sobject. This corresponds toPOST /crypto/v1/verifyin the REST API.The arguments to this method are described in the REST API docs for
VerifyRequest.Returns a object corresponding to
VerifyResponsein the REST API on success, orniland anErrorif the validity of the signature could not be established.Note that if the signature is determined to be invalid, the call will successfully return
{ result = false }.For example,
local sobject = assert(Sobject { name = "my rsa key" }) local verify_response = assert(sobject:verify { data = Blob.from_bytes("hello world"), signature = signature, hash_alg = 'SHA256' }) assert(verify_response.result == true)sobject:wrap { ... }Use
sobjectto wrap another security object. This corresponds toPOST /crypto/v1/wrapkeyin the REST API.The arguments to this method are described in the REST API docs for
WrapKeyRequestEx.The
subjectargument may be a descriptor (as described in the REST API) or aSobject.Returns an object corresponding to
WrapKeyResponsein the REST API on success, orniland anErrorif the sobject key could not be wrapped.For example,
local wrapping_key = assert(Sobject { name = "AES wrapping key" }) local generated_key = assert(Sobject.create { obj_type = 'AES', key_size = 128, transient = true }) local wrap_response = assert(wrapping_key:wrap { subject = generated_key, mode = 'CBC' }) local result = wrap_response.wrapped_key:base64() .. ':' .. wrap_response.iv:base64()sobject:unwrap { ... }Use
sobjectto unwrap and import a wrapped key. This corresponds toPOST /crypto/v1/unwrapkeyin the REST API.The arguments to this method are described in the REST API docs for
UnwrapKeyRequest.Returns the unwrapped
Sobject, orniland anErrorif the wrapped key could not be unwrapped.For example,
function run(input) local wrapping_key = assert(Sobject { name = "RSA wrapping key" }) local unwrapped_key = assert(wrapping_key:unwrap { wrapped_key = input.wrapped_key, obj_type = 'AES', transient = true }) return unwrapped_key:encrypt(input.encrypt_request) endsobject:agree { ... }Perform a key agreement algorithm using the private key
sobjectand the public key from another party. This corresponds toPOST /crypto/v1/agreein the REST API.The arguments to this method are described in the REST API for
AgreeKeyRequest.The
public_keyargument may be a descriptor (as described in the REST API) or aSobject.Returns the agreed
Sobject, orniland anErrorif the key agreement could not be completed.sobject:mac { ... }Use
sobjectto compute a cryptographic Message Authentication Code on a message.sobjectmust be a symmetric key. This corresponds toPOST /crypto/v1/macin the REST API.The arguments to this method are described in the REST API docs for
MacGenerateRequest.Returns an object corresponding to
MacGenerateResponsefrom the REST API on success, orniland anErrorif the MAC could not be generated.sobject:mac_verify { ... }Use
sobjectto verify a cryptographic Message Authentication Code on a message.sobjectmust be a symmetric key. This corresponds toPOST /crypto/v1/macverifyin the REST API.The arguments to this method are described in the REST API docs for
MacVerifyRequest.Returns an object corresponding to
MacVerifyResponsefrom the REST API on success, orniland anErrorif the validity of the MAC could not be established.Note that if the MAC is determined to be invalid, the call will successfully return
{ result = false }.sobject:derive { ... }Use
sobjectto derive another key. This corresponds toPOST /crypto/v1/derivein the REST API.The arguments to this method are described in the REST API docs for
DeriveKeyRequest.Returns a newly created
Sobject, orniland anErrorif the key could not be derived.For example,
function run(input) local sobject = assert(Sobject.create { name = "test_key_20230314", obj_type = "AES", key_size = 256 }) local derive_response = assert(sobject:derive { name = "derived_key_20230314", key_size = 256, key_type = "AES", mechanism = { encrypt_data = { alg = "AES", mode = "CBC", iv = "6pduXJMk351eaTbb0XLozw==", plain = "OjKlKPBH89/K8QfdxiutVyD/yAMg0k+NH7xeFEsR0hxkUwOaen+Uv/uXCvPUNj53"}}}) return derive_response endsobject:agree { ... }Sobject:issuer_dn()If the object is a certificate, then returns an
X509Namerepresenting the certificate issuer.Sobject:subject_dn()If the object is a certificate, then returns an
X509Namerepresenting the certificate subject.Sobject:get_extension(oid)If the object is a certificate, then returns the value of the extension specified by the
OIDargument.For example:
let extn = sobject.get_extension(Oid.from_str('subjectKeyIdentifier'))Sobject:extension_oids()If the object is a certificate, returns a list of
OIDs which are used in the certificate.For example:
let extn_oids = sobject.extension_oids()Sobject:verify_certificate(issuing_cert)If the object is a certificate, then verifies the signature on the cert using the issuing certificate which is provided as an argument.
2.13 The EntityId Class
This identifies an app, user, or plugin. It corresponds to CreatorType in the REST API docs.
Methods
entity_id:id()Returns the UUID of the app, user, or plugin.
entity_id:type()Returns
"app","user", or"plugin".entity_id:entity()Returns the appropriate
App,User, orPluginobject, orniland anErrorif no such object exists.For example,
local key = assert(Sobject { name = "my key" }) local creator = key.creator return "Created by a " .. creator:type() .. " named " .. creator:entity().name
2.14 The App Class
This represents an app.
The properties of this object are described in the REST API docs.
app.creator is an EntityId object.
Constructors
App { id = "<uuid>" }This returns the app with the given UUID, or
niland anErrorif no such app exists.
2.15 The Plugin Class
This represents a Plugin.
The properties of this object are described in the REST API docs.
plugin.creator is an EntityId object.
Constructors
Plugin { id = "<uuid>" }This returns the plugin with the given UUID, or
niland anErrorif no such object exists.
2.16 The User Class
This represents a User.
The properties of this object are described in the REST API docs.
Constructors
User { id = "<uuid>" }This returns the user with the given UUID, or
niland anErrorif no such user exists.
2.17 The Group Class
This represents a Group.
The properties of this object are described in the REST API docs.
Constructors
Group { id = "<uuid>" }This returns the group with the given UUID, or
niland anErrorif no such group exists.
2.18 The Function digest
Compute the digest (hash) of the given data using the given algorithm. This corresponds to POST /crypto/v1/digest from the REST API.
digest returns an object corresponding to DigestResponse from the REST API on success, or nil and an Error if the digest could not be computed. The returned digest is a Blob.
For example,
local sha256_hash = assert(digest { data = Blob.from_bytes('Hello world'), alg = 'SHA256' }).digest
return sha256_hash:hex()2.19 The BigNum Class
A BigNum represents an arbitrary-precision integer.
Constructors
BigNum.from_bytes_be(string)Constructs a
BigNumfrom a Lua string with big endian binary data.For example:
local bignum = BigNum.from_bytes_be('\x01\x00\x01').BigNum.from_bytes_be(blob)Constructs a
BigNumfrom a Blob with big endian binary data.E.g.:
local bignum = BigNum.from_bytes_be(Blob.from_bytes('\x01\x00\x01')).
Methods
bignum:to_bytes_be()Returns a Blob with unsigned binary data, big endian..
For example:
local bytes = bignum:to_bytes_be().bignum:to_bytes_be_zero_pad(n)Returns a Blob with unsigned binary data, big endian, zero padding to n bytes.
For example:
local bytes = bignum:to_bytes_be_zero_pad(16).bignum:copy()Returns a copy of bignum.
For example: `local copy = bignum:copy().
bignum == bignumCompare two bignums for equality
For example:
local same = bignum1 == bignum2bignum < bignumTest if one bignum is less than another
For example:
local less = bignum1 < bignum2bignum <= bignumTest if one bignum is less than or equal to another
For example:
local lte = bignum1 <= bignum2bignum > bignumTest if one bignum is greater than another
For example:
local greater = bignum1 > bignum2bignum >= bignumTest if one bignum is greater than or equal to another
For example:
local gte = bignum1 >= bignum2bignum:add(bignum2)Performs a signed addition of bignum2 to bignum and updates bignum to result.
For example:
bignum:add(bignum2).bignum + bignum2Performs a signed addition of bignum2 to bignum and returns result.
For example:
local result = bignum + bignum2.bignum:sub(bignum2)Performs a signed subtraction of bignum2 from bignum and updates bignum to result.
For example:
bignum:sub(bignum2).bignum - bignum2Performs a signed subtraction of bignum2 from bignum and returns result.
For example:
local result = bignum - bignum2.bignum:mul(bignum2)Performs a multiplication of bignum by bignum2 and updates bignum to result.
For example:
bignum:mul(bignum2).bignum * bignum2Performs a multiplication of bignum by bignum2 and returns result.
For example:
local result = bignum * bignum2.bignum:div()Performs a division of bignum by bignum2 and updates bignum to result.
For example:
bignum:div().bignum / bignum2Performs a division of bignum by bignum2 and returns result.
For example:
local result = bignum / bignum2.bignum:mod()Performs a modular reduction of bignum for the base bignum2 and updates bignum to result.
For example:
bignum:mod().bignum % bignum2Performs a modular reduction of bignum for the base bignum2 and returns result.
For example:
local result = bignum % bignum2.
2.20 The Time Class
A Time represents current system time.
Constructors
Time.now_insecure()Constructs a
Time.For example:
local time = Time.now_insecure().Time.from_iso8601(date_time)Constructs a
Timerepresenting the provided date/time which must be in ISO-8601 format. Returns an error if the provided input does not have the correct format or is outside the range supported byTime. Note thatTimedoes not support date/times before Jan 1, 1970 00:00:00 UTC or after Apr 11, 2262 23:47:16 UTC.For example:
local time = assert(Time.from_iso8601('20060102T150405Z')).
Methods
time:unix_epoch_seconds()Returns a Lua number representing
timeas seconds since Jan 1, 1970 00:00:00 UTC.For example:
local secs = time:unix_epoch_seconds().time:unix_epoch_nanoseconds()Returns a Lua number representing
timeas nanoseconds since Jan 1, 1970 00:00:00 UTC.For example:
local nanosecs = time:unix_epoch_nanoseconds().time:to_iso8601()Returns a Lua string representing
timein ISO-8601 format.For example.:
local now = Time.now_insecure() return now:to_iso8601() -- for example: "20210914T193810Z"time:add_seconds(seconds)Returns a new
Timeinstance representingsecondsaftertime. Note thattimeis not modified.For example:
local t0 = assert(Time.from_iso8601('20060102T150405Z')) local t1 = t0:add_seconds(30) assert(t1:to_iso8601() == '20060102T150435Z')
2.21 The OID Class
An OID represents an ASN.1 object identifier.
Constructors
Oid.from_str(str)Load an OID from a string which can be either the usual dotted decimal notation or a name (for some well known and commonly used OIDs).
For example:
local cn = Oid.from_str('CN')E.g.:local email = Oid.from_str('1.2.840.113549.1.9.1')
Methods
Oid:to_str()If the OID has a known value, return the string associated with it. Otherwise returns the dotted decimal value.
For example:
assert(email:to_str() == 'emailAddress')
2.22 The DerEncoder Class
A DerEncoder allows creating DER encodings of arbitrary objects. This is useful when creating X.509 certificate extensions, or when the plugin wants to return an ASN.1 structure such as a CAdES signature.
Constructors
DerEncoder:new()Return a new
DerEncoderobject
Methods
DerEncoder:value()This returns the binary blob of the DER encoding so far. It is possible to call value more than once, and it is also possible to add more data to a
DerEncoderafter callingvalue. The sequences (calls tostart_seq/end_seq) must be matched.DerEncoder:start_seq()Being a new DER SEQUENCE structure. This can be nested.
DerEncoder:end_seq()End the currently active SEQUENCE
DerEncoder:implicit_context(tag)Tag the following datum with an implicit context-specific tag. For example
DerEncoder.new():implicit_context(1):start_seq():implicit_context(2):add_int(2):end_seq()will tag the SEQUENCE with a context specific tag of 1 and the INTEGER with a context specific tag of 2. The tag should be given as a non-negative integer.DerEncoder:explicit_context(tag)Similar to
implicit_contextexpect using explicit DER tagging.DerEncoder:add_bool(b)Add a boolean value to the current DER encoding.
DerEncoder:add_int(i)Add an integer value to the current DER encoding. This can be either a Lua integer or a
BigNumobject.DerEncoder:add_oid(oid)Add an
Oidto the encoding.DerEncoder:add_enum(en)Add an enum to the encoding; the enum should be specified as a Lua integer.
DerEncoder:add_string(str, encoding)Add a string to the encoding. The
encodingparameter specifies what ASN.1 string type to use; 'utf8', 'printable', 'numeric' or 'ia5'. If the encoding paramter is not given or is nil, default to 'utf8'.Be aware that when using 'printable' or 'numeric', the encoder does not check that the string satisfies the content requirements; it is possible to insert arbitrary data with 'numeric' or 'printable' types, which is an invalid DER encoding and is likely to be rejected by at least some parsers.
DerEncoder:add_octets(input)Add an OCTET STRING. The argument can be either a
Blobor a Lua string. If the argument is a string it is assumed to be a hexadecimal input.DerEncoder:add_raw(input)Insert arbitrary values into the DER encoding stream. The argument is directly inserted into the stream as-is with no inspection. This allows encoding values that are not expressible using the current API.
2.23 The X509Name Class
An X509Name represents an X.509 distinguished name, as used in certificates and signing requests.
Constructors
X509Name.from_der(der)Load a DN from the binary DER encoding.
For example:
local name = X509Name.from_der(der)X509Name.new()Create a new (empty) DN.
For example:
local name = X509Name.new()
Methods
X509Name.to_str()Return a readable string encoding for the DN.
X509Name.oids()Return an array of OIDs specifying the values it contains
X509Name.get(oid)If the specified OID is set in the DN, return the value. Otherwise returns nil.
X509Name.set(oid, value, string_type)Set the OID to the provided value. The
string_typefield specifies how the string is encoded; for best interop use "utf8". Other accepted encodings are "printable", "numeric", and "ia5".For example:
name.set(oids.from_str('CN'), 'localhost', 'utf8')
2.24 The Pkcs10Csr Class
A Pkcs10Csr represents a certificate signing request as specified in PKCS 10.
Constructors
Pkcs10Csr.new(key_id, subject)Pkcs10Csr.from_der(der)
Load a PKCS10 CSR from the binary DER value
Methods
csr:verify_signature()Verify the self-signature on a CSR, returning true on success. This simply verifies that whoever generated the CSR does have access to the private key corresponding to the public key in the CSR. This does not authenticate the CSR in any way. However, this prevents certain forgery attacks that are otherwise possible.
csr:subject_dn()Return an
X509Nameof the subject set in the CSR.csr:sign(ca_cert, ca_key_name, cert_lifetime, digest, serial_bits, copy_extensions)Sign a CSR, creating a new certificate object. The
ca_certshould be a CA certificate Sobject. Theca_key_nameshould be the name of a key stored in DSM which is associated with the issuing CA certificate. The certificate lifetime is given in seconds from the current time.The
digestparameter specifies what hash function to use when signing the certificate. If not specified (ornil), then SHA-256 is used by default.The
copy_extensionsparameter's default value is false if not provided. This parameter determines if the extensions from CSR should be copied into a signed certificate or not.
2.25 The TbsCertificate Class
A TbsCertificate represents the body of a certificate without an associated signature. It allows creating a custom certificate without first creating a CSR.
Constructors
TbsCertificate.new(key_id, subject, skid, lifetime, serial_bits_size )The parameters have the following descriptions:
key_id: A security object descriptor (for example, a key name) for an asymmetric key.subject: A string representing the X.509 distinguished name for the certificate (that is, information about the certificate owner).skid: A string representing the “subject key identifier” (SKID) which will be included in the extensions of the resulting certificate. For details on generating this value, refer to https://www.rfc-editor.org/rfc/rfc3280#section-4.2.1.2.lifetime: An integer representing the validity period of the certificate.serialize_size: the size of the certificate’s serial number in bits. The parameter must be an integer. Valid values range from 8 to 160. If not provided, the default value is 160.
Methods
TbsCertificate:add_extension(oid, critical, value)Add an extension to the TbsCertificate.
Each certificate extension is identified by an OID and is marked either critical or non-critical. During validation, any implementation that encounters a critical extension it cannot identify is required to reject the certificate. So ordinarily critical extensions are only used when the certificate cannot be safely validated without understanding the extension. If the extension is just for informative purposes it is usually non-critical.
The value must be the complete DER encoding of the extension. This can be creating using DerEncoder, for example to insert a critical
basicConstraintsextension,local bc = DerEncoder.new():start_seq():add_bool(false):end_seq():value() tbs:add_extension(Oid.from_str('basicConstraints'), true, bc)TbsCertificate:to_der()Encode the TbsCertificate as a DER value
TbsCertificate:sign(ca_cert, ca_key_name, serial_bits, digest)Sign the TbsCertificate and return a new certificate object. The issuing certificate should be an Sobject and the
ca_key_name givesthe name of the signing key which must be stored in Fortanix DSM.If
serial_bitsis set to a positive integer, then a new serial number is created of the specified bit size. If it is set totrue, then a new serial of some default size (currently 160 bits) is used. If it is set to0orfalse, then the serial number set in the original TbsCertificate structure is used in the final signed certificate.The
digestparameter specifies what hash function to use when signing the certificate. If not specified (ornil), then SHA-256 is used by default.TbsCertificate:self_sign(signing_key, digest)Self-sign a TbsCertificate, creating a new self-signed certificate, that is, with the issuer DN set the same as the subject DN that was provided when the
newconstructor was invoked.The
signing_keymust be a reference to the private key which will be used to generate the signature.The
digestparameter specifies what hash function to use when signing the certificate. If not specified (ornil), then SHA-256 is used by default.
2.26 The Tr31Envelope Class
This represents a TR-31 cryptogram for key wrapping (refer to ANSI X9.143-2022). Wrapping a security object is achieved with the Tr31Envelope.new constructor followed by the seal function. Conversely, to unwrap an existing cryptogram, the function tr31_open is used.
Constructors
Tr31Envelope.newThis prepares a TR-31 header to subsequently wrap an existing security object. Refer to ANSI X9.143-2022 for the meaning of these fields.
local envelope = Tr31Envelope.new(version, key_usage, algorithm, mode_of_use, key_version_number, exportability, key_context)
Methods
Tr31Envelope.sealGiven a wrapping key and a target key (both residing in DSM),
sealis used to perform the wrapping. It outputs the resulting TR-31 cryptogram, or an error.local envelope = Tr31Envelope.new(version, key_usage, algorithm, mode_of_use, key_version_number, exportability, key_context) local cryptogram, err = envelope:seal(wrapping_sobject, target_sobject)
2.27 The tr31_open Function
Given a TR-31 cryptogram and the corresponding unwrapping key (residing in DSM), this function unwraps the cryptogram, obtaining the unwrapped key bytes.
local key_bytes, err = tr31_open(unwrapping_sobject, cryptogram)2.28 The EcGroup Class
An EcGroup represents some elliptic curve group supported in Fortanix DSM.
Constructors
EcGroup.SecP256R1,EcGroup.SecP256K1, ...EcGroup supports construction using a enum-style syntax. Possible values are "
Bp256R1", "Bp384R1", "Bp512R1", "Curve25519", "SecP192K1", "SecP192R1", "SecP224K1", "SecP224R1", "SecP256K1", "SecP256R1", "SecP384R1", and "SecP521R1".EcGroup.from_nameThis function constructs an EC group from a string (values matching the above named enums). This is useful when the desired group is provided as a parameter by the invoker of a plugin.
Methods
EcGroup:name()This function returns the name of the group.
EcGroup:generator()This function returns an
EcPointwhich is the generator of this group.EcGroup:point_from_components(x, y)This function returns a new
EcPointtakingBigNumcomponentsxandy.EcGroup:point_from_binary(v)This function decodes an encoded EC point using the standard
OS2ECPconvention. Both compressed and uncompressed points are supported.
2.29 The EcPoint Class
An EcPoint represents a particular point on an elliptic curve. It is constructed using the functions on EcGroup.
Methods
EcPoint:group()This function returns the
EcGroupthat this point belongs to.EcPoint:add(other)This function performs point addition, adding two points and returning the resulting point.
EcPoint:mul(scalar)This function performs point multiplication, multiplying a point by a scalar
BigNumand returning the resulting point.EcPoint:x()This function returns the affine x coordinate of the point as a
BigNum.EcPoint:y()This function returns the affine y coordinate of the point as a
BigNum.EcPoint:to_binary(compress)This function returns the binary encoding of a point. If
compressisfalsethen uncompressed encoding is used. Ifcompressisnilortruethen point compression is used.
2.30 The Function require_approval_for
You can call this function in your function check to require the caller to submit an approval request to invoke the plugin. For example:
function check(input)
local key = assert(Sobject { name = 'my key' })
require_approval_for(key)
endIn the above example, if my key is subject to an approval policy, then the caller needs to submit an approval request to invoke the plugin and the plugin is invoked once enough reviewers approve that request.
2.31 The request Function
The request function allows for HTTPS requests to be made to external servers. It consumes a Lua table with the following fields:
method: a string indicating the HTTP method (for example,"POST")url: the URL of the resource or server.headers: a table of any HTTP headers required for the request. If no headers are needed, useheaders = {})body: this payload for the request, which can be either:a single string value (for example,
some_value=<base64 input>)a JSON encoded block, useful for payloads with multiple fields (for example,
body = json.encode({ field1 = input.field1, field2 = some_other_data}))
For example:
function run(input)
local url = 'https://apac.smartkey.io/sys/v1/version'
local headers = {}
headers['Content-Type'] = 'application/json'
local response, err = request { method = 'GET', url = url, headers = headers }
return response
end