trust_anchors: XML and certificate tests for bootstrap

At the moment tests are quite dumb and test only basic HTTPS certificate
validation and XML parsing/error detection.

We need to refactor code first to allow more detailed testing.

FIXME: webserv.lua is not terminated when tests are finished
parent af5055fc
......@@ -70,6 +70,12 @@ local function parse_xml_keydigest(attrs, inside, output)
assert(fields['validFrom'],
string.format('mandatory KeyDigest XML attribute validFrom ' ..
'not found in "%s"', attrs))
local valid_attrs = {id = true, validFrom = true, validUntil = true}
for key, _ in pairs(fields) do
assert(valid_attrs[key],
string.format('unsupported KeyDigest attribute "%s" found in "%s"',
key, attrs))
end
_, n = string.gsub(inside, "<([%w]+).->([^<]+)</[%w]+>", function (k, v) fields[k] = v end)
assert(n >= 1,
......
-- check prerequisites
local has_http = pcall(require, 'http') and pcall(require, 'http.request')
if not has_http then
pass('skipping bootstrap tests because http module is not not installed')
done()
end
local cqueues = require("cqueues")
local socket = require("cqueues.socket")
-- unload modules which are not related to this test
if ta_signal_query then
modules.unload('ta_signal_query')
end
if priming then
modules.unload('priming')
end
if detect_time_skew then
modules.unload('detect_time_skew')
end
-- Self-checks on globals
assert(help() ~= nil)
assert(worker.id ~= nil)
-- Self-checks on facilities
assert(worker.stats() ~= nil)
assert(net.interfaces() ~= nil)
-- Self-checks on loaded stuff
assert(#modules.list() > 0)
-- Self-check timers
ev = event.recurrent(1 * sec, function () return 1 end)
event.cancel(ev)
ev = event.after(0, function () return 1 end)
-- do not attempt to contact outside world using DNS, operate only on cache
net.ipv4 = false
net.ipv6 = false
-- do not listen, test is driven by config code
env.KRESD_NO_LISTEN = true
-- start test webserver
local function start_webserver()
-- srvout = io.popen('luajit webserv.lua')
-- TODO
os.execute('luajit webserv.lua &')
-- assert(srvout, 'failed to start webserver')
end
local function wait_for_webserver()
local starttime = os.time()
local connected = false
while not connected and os.difftime(os.time(), starttime) < 5 do
local con = socket.connect("localhost", 8053)
connected, msg = pcall(con.connect, con, 5)
cqueues.sleep (0.3)
end
assert(connected, string.format('unable to connect to web server: %s', msg))
end
local host = 'https://localhost:8053/'
-- avoid interference with configured KEYFILE_DEFAULT
trust_anchors.keyfile_default = nil
local function test_err_cert()
trust_anchors.bootstrap_ca = 'x509/wrongca.pem'
trust_anchors.bootstrap_url = host .. 'ok1.xml'
boom(trust_anchors.add_file, {'ok1.keys'},
'fake server certificate is detected')
end
local function test_err_xml(testname, testdesc)
return function()
trust_anchors.bootstrap_ca = 'x509/ca.pem'
trust_anchors.bootstrap_url = host .. testname .. '.xml'
boom(trust_anchors.add_file, {testname .. '.keys'}, testdesc)
end
end
-- dumb test, right now it cannot check content of keys because
-- it does not get written until refresh fetches DNSKEY from network
-- (and bypassing network using policy bypasses also validation
-- so it does not test anything)
local function test_ok_xml(testname, testdesc)
return function()
trust_anchors.bootstrap_url = host .. testname .. '.xml'
same(trust_anchors.add_file(testname .. '.keys'), nil, testdesc)
end
end
return {
start_webserver,
wait_for_webserver,
test_err_cert,
test_err_xml('err_attr_extra_attr', 'bogus TA XML with an extra attribute'),
test_err_xml('err_attr_validfrom_invalid', 'bogus TA XML with invalid validFrom value'),
test_err_xml('err_attr_validfrom_missing', 'bogus TA XML without mandatory validFrom attribute'),
test_err_xml('err_elem_extra', 'bogus TA XML with an extra element'),
test_err_xml('err_elem_missing', 'bogus TA XML without mandatory element'),
test_err_xml('err_multi_ta', 'bogus TA XML with multiple TAs'),
test_err_xml('unsupp_nonroot', 'unsupported TA XML for non-root zone'),
test_err_xml('unsupp_xml_v11', 'unsupported TA XML with XML v1.1'),
test_err_xml('ok0_badtimes', 'TA XML with no valid keys'),
test_ok_xml('ok1_expired1', 'TA XML with 1 valid and 1 expired key'),
test_ok_xml('ok1_notyet1', 'TA XML with 1 valid and 1 not yet valid key'),
test_ok_xml('ok1', 'TA XML with 1 valid key'),
test_ok_xml('ok2', 'TA XML with 2 valid keys'),
}
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="FC4A93EC-9F4E-4597-A766-AD6723E4A56E" source="https://localhost/err_attr_extra_attr.xml">
<Zone>.</Zone>
<KeyDigest unknownattr="test" id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00">
<KeyTag>19036</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
</KeyDigest>
<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
<KeyTag>20326</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="ABD668AB-52DF-4A59-80E3-16CE6341BC55" source="https://localhost/err_attr_validfrom_invalid.xml">
<Zone>.</Zone>
<KeyDigest id="Kjqmt7v" validFrom="2010-07-32T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00">
<KeyTag>19036</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
</KeyDigest>
<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
<KeyTag>20326</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="3513058C-4041-40CC-AF0A-D3CCD70F962B" source="https://localhost/err_attr_validfrom_missing.xml">
<Zone>.</Zone>
<KeyDigest id="Kjqmt7v" validUntil="2019-01-11T00:00:00+00:00">
<KeyTag>19036</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
</KeyDigest>
<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
<KeyTag>20326</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="B1854D58-1867-4FA7-872F-0099D394114D" source="https://localhost/err_elem_extra.xml">
<Zone>.</Zone>
<KeyDigest id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00">
<KeyTag>19036</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
</KeyDigest>
<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
<KeyTag>20326</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
<UnknownElement>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</UnknownElement>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="BB074095-3A42-4B13-9CC1-CFFF644D4D54" source="https://localhost/err_elem_missing.xml">
<Zone>.</Zone>
<KeyDigest id="Kjqmt7v" validFrom="2010-07-15T00:00:00+00:00" validUntil="2019-01-11T00:00:00+00:00">
<KeyTag>19036</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5</Digest>
</KeyDigest>
<KeyDigest id="Klajeyz" validFrom="2017-02-02T00:00:00+00:00">
<KeyTag>20326</KeyTag>
<Algorithm>8</Algorithm>
<!-- this element is missing: DigestType>2</DigestType-->
<Digest>E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="9DCE46E1-FC78-48E1-81B5-94E328790BB5" source="https://localhost/err_multi_ta.xml">
<Zone>.</Zone>
<KeyDigest id="1" validFrom="2000-02-02T00:00:00+00:00">
<KeyTag>2</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
</KeyDigest>
</TrustAnchor>
<TrustAnchor id="9DCE46E1-FC78-48E1-81B5-94E328790BB5" source="https://localhost/err_multi_ta.xml">
<Zone>test.</Zone>
<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00">
<KeyTag>2</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="EDEDAA08-D2A0-421E-81DC-AF11F5A0CDCD" source="https://localhost/ok0_badtimes.xml">
<Zone>.</Zone>
<KeyDigest id="E" validFrom="2000-01-01T00:00:00+00:00" validUntil="2000-01-01T00:00:00+00:00">
<KeyTag>1</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE</Digest>
</KeyDigest>
<KeyDigest id="F" validFrom="2001-01-01T00:00:00+00:00" validUntil="2001-01-01T00:00:00+00:00">
<KeyTag>2</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="82E6CB77-12DF-4E61-BF49-367FB95A8BAA" source="https://localhost/ok1.xml">
<Zone>.</Zone>
<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00">
<KeyTag>2</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="68463155-A857-4C7E-BCA6-2F6CC2EAC1BE" source="https://localhost/ok1_expired1.xml">
<Zone>.</Zone>
<KeyDigest id="F" validFrom="1990-01-01T00:00:00+00:00" validUntil="2000-01-01T00:00:00+00:00">
<KeyTag>1</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Digest>
</KeyDigest>
<KeyDigest id="1" validFrom="2000-01-01T00:00:00+00:00">
<KeyTag>2</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="507B39D5-049E-467C-9E9A-F5BE597C9DDA" source="https://localhost/ok1_notyet1.xml">
<Zone>.</Zone>
<KeyDigest id="1" validFrom="2010-07-15T00:00:00+00:00">
<KeyTag>1</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
</KeyDigest>
<KeyDigest id="2" validFrom="2050-12-31T23:59:59+00:00">
<KeyTag>2</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="1DECEB91-0591-44A1-95CF-1788337514B8" source="https://localhost/ok2.xml">
<Zone>.</Zone>
<KeyDigest id="K1" validFrom="2010-07-15T00:00:00+00:00">
<KeyTag>1</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
</KeyDigest>
<KeyDigest id="K2" validFrom="2011-02-02T00:00:00+00:00">
<KeyTag>2</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>2222222222222222222222222222222222222222222222222222222222222222</Digest>
</KeyDigest>
</TrustAnchor>
for F in *.xml; do sed -i "s/TrustAnchor id=\"[^\"]*\"/TrustAnchor id=\"$(uuidgen | tr '[[:lower:]]' '[[:upper:]]')\"/" $F; done
for F in *.xml; do sed -i "s#source=\"[^\"]*\"#source=\"https://localhost/$F\"#" $F; done
<?xml version="1.0" encoding="UTF-8"?>
<TrustAnchor id="8449BFB8-FD6C-4082-B0FE-1A3E3399203B" source="https://localhost/unsupp_nonroot.xml">
<Zone>test.</Zone>
<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00">
<KeyTag>2</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
</KeyDigest>
</TrustAnchor>
<?xml version="1.1" encoding="UTF-8"?>
<TrustAnchor id="3612AE1C-E8F3-4FD8-B8CD-96C7FDACC7A5" source="https://localhost/unsupp_xml_v11.xml">
<Zone>.</Zone>
<KeyDigest id="2" validFrom="2000-02-02T00:00:00+00:00">
<KeyTag>2</KeyTag>
<Algorithm>8</Algorithm>
<DigestType>2</DigestType>
<Digest>1111111111111111111111111111111111111111111111111111111111111111</Digest>
</KeyDigest>
</TrustAnchor>
-- This is a module that does the heavy lifting to provide an HTTP/2 enabled
-- server that supports TLS by default and provides endpoint for other modules
-- in order to enable them to export restful APIs and websocket streams.
-- One example is statistics module that can stream live metrics on the website,
-- or publish metrics on request for Prometheus scraper.
local http_server = require('http.server')
local http_headers = require('http.headers')
local http_websocket = require('http.websocket')
local http_util = require "http.util"
local x509, pkey = require('openssl.x509'), require('openssl.pkey')
-- Module declaration
local M = {}
-- Export HTTP service endpoints
M.endpoints = {
['/'] = {'text/html', 'test'},
}
-- Serve known requests, for methods other than GET
-- the endpoint must be a closure and not a preloaded string
local function serve(endpoints, h, stream)
local hsend = http_headers.new()
local path = h:get(':path')
local entry = endpoints[path]
if not entry then -- Accept top-level path match
entry = endpoints[path:match '^/[^/?]*']
end
-- Unpack MIME and data
local data, mime, ttl, err
if entry then
mime = entry[1]
data = entry[2]
ttl = entry[4]
end
-- Get string data out of service endpoint
if type(data) == 'function' then
local set_mime, set_ttl
data, err, set_mime, set_ttl = data(h, stream)
-- Override default endpoint mime/TTL
if set_mime then mime = set_mime end
if set_ttl then ttl = set_ttl end
-- Handler doesn't provide any data
if data == false then return end
if type(data) == 'number' then return tostring(data), err end
-- Methods other than GET require handler to be closure
elseif h:get(':method') ~= 'GET' then
return '501', ''
end
if not mime or type(data) ~= 'string' then
return '404', ''
else
-- Serve content type appropriately
hsend:append(':status', '200')
hsend:append('content-type', mime)
hsend:append('content-length', tostring(#data))
if ttl then
hsend:append('cache-control', string.format('max-age=%d', ttl))
end
assert(stream:write_headers(hsend, false))
assert(stream:write_chunk(data, true))
end
end
-- Web server service closure
local function route(endpoints)
return function (_, stream)
-- HTTP/2: We're only permitted to send in open/half-closed (remote)
local connection = stream.connection
if connection.version >= 2 then
if stream.state ~= 'open' and stream.state ~= 'half closed (remote)' then
return
end
end
-- Start reading headers
local h = assert(stream:get_headers())
local m = h:get(':method')
local path = h:get(':path')
-- Upgrade connection to WebSocket
local ws = http_websocket.new_from_stream(stream, h)
if ws then
assert(ws:accept { protocols = {'json'} })
-- Continue streaming results to client
local ep = endpoints[path]
local cb = ep[3]
if cb then
cb(h, ws)
end
ws:close()
return
else
local ok, err, reason = http_util.yieldable_pcall(serve, endpoints, h, stream)
if not ok or err then
print(string.format('%s err %s %s: %s (%s)', os.date(), m, path, err or '500', reason))
-- Method is not supported
local hsend = http_headers.new()
hsend:append(':status', err or '500')
if reason then
assert(stream:write_headers(hsend, false))
assert(stream:write_chunk(reason, true))
else
assert(stream:write_headers(hsend, true))
end
else
print(string.format('%s ok %s %s', os.date(), m, path))
end
end
end
end
-- @function Prefer HTTP/2 or HTTP/1.1
local function alpnselect(_, protos)
for _, proto in ipairs(protos) do
if proto == 'h2' or proto == 'http/1.1' then
return proto
end
end
return nil
end
-- @function Create TLS context
local function tlscontext(crt, key)
local http_tls = require('http.tls')
local ctx = http_tls.new_server_context()
if ctx.setAlpnSelect then
ctx:setAlpnSelect(alpnselect)
end
assert(ctx:setPrivateKey(key))
assert(ctx:setCertificate(crt))
return ctx
end
-- @function Listen on given HTTP(s) host
function M.add_interface(conf)
local crt, key
if conf.tls ~= false then
assert(conf.cert, 'cert missing')
assert(conf.key, 'private key missing')
-- Check if a cert file was specified
-- Read x509 certificate
local f = io.open(conf.cert, 'r')
if f then
crt = assert(x509.new(f:read('*all')))
f:close()
-- Continue reading key file
if crt then
f = io.open(conf.key, 'r')
key = assert(pkey.new(f:read('*all')))
f:close()
end
end
-- Check loaded certificate
assert(crt and key,
string.format('failed to load certificate "%s"', conf.cert))
end
-- Compose server handler
local routes = route(conf.endpoints or M.endpoints)
-- Check if UNIX socket path is used
local addr_str
if not conf.path then
conf.host = conf.host or 'localhost'
conf.port = conf.port or 8053
addr_str = string.format('%s@%d', conf.host, conf.port)
else
if conf.host or conf.port then
error('either "path", or "host" and "port" must be provided')
end
addr_str = conf.path
end
-- Create TLS context and start listening
local s, err = http_server.listen {
-- cq = worker.bg_worker.cq,
host = conf.host,
port = conf.port,
path = conf.path,
v6only = conf.v6only,
unlink = conf.unlink,
reuseaddr = conf.reuseaddr,
reuseport = conf.reuseport,
client_timeout = conf.client_timeout or 5,
ctx = crt and tlscontext(crt, key),
tls = conf.tls,
onstream = routes,
-- Log errors, but do not throw
onerror = function(myserver, context, op, err, errno) -- luacheck: ignore 212
local msg = '[http] ' .. op .. ' on ' .. tostring(context) .. ' failed'
if err then
msg = msg .. ': ' .. tostring(err)
end
print(msg)
end,
}
-- Manually call :listen() so that we are bound before calling :localname()
if s then
err = select(2, s:listen())
end
assert(not err, string.format('failed to listen on %s: %s', addr_str, err))
return s
end
-- init
local files = {
'ok0_badtimes.xml',
'ok1.xml',
'ok1_expired1.xml',
'ok1_notyet1.xml',
'ok2.xml',
'err_attr_validfrom_missing.xml',
'err_attr_validfrom_invalid.xml',
'err_attr_extra_attr.xml',
'err_elem_missing.xml',
'err_elem_extra.xml',
'err_multi_ta.xml',
'unsupp_nonroot.xml',
'unsupp_xml_v11.xml'
}
-- Export static pages specified at command line
for _, name in ipairs(files) do
local fd = io.open(name)
assert(fd, string.format('unable to open file "%s"', name))
M.endpoints['/' .. name] = { 'text/xml', fd:read('*a') }
fd:close()
end
local server = M.add_interface({
host = 'localhost',
port = 8053,
tls = true,
cert = 'x509/server.pem',
key = 'x509/server-key.pem'
})
server:loop()
Public Key Info:
Public Key Algorithm: RSA
Key Security Level: High (3072 bits)
modulus:
00:cd:d7:b2:a4:37:ae:47:8b:c2:e2:24:7a:2e:77:4a
9a:aa:8e:5a:f5:68:80:f0:7c:68:08:50:30:3a:f9:77
bb:99:a6:67:f7:ce:45:6a:c9:11:43:81:4e:a7:92:53
58:53:0c:76:fe:81:9c:a5:c8:32:e9:2e:5d:1d:16:3e
5d:b2:21:c6:65:d7:33:c1:04:81:7a:8e:8c:c7:2e:8c
d0:73:bf:37:43:b7:1b:07:90:cb:a3:6e:7c:67:76:be
b0:20:28:5b:5d:d7:b3:b2:09:5e:2b:23:76:fd:32:26
c7:cf:de:e8:0c:5b:c2:bc:a8:52:e2:58:56:89:ad:57
63:b0:01:86:b3:d1:3d:e0:6c:13:ff:b5:e6:37:f4:87
64:f5:d8:9e:8c:7d:48:43:60:5c:bb:bb:73:cc:d3:3f
ad:76:44:ff:08:25:b8:dc:c4:47:c2:29:29:b8:00:4b
a7:01:84:93:40:9c:80:35:78:7d:b2:bb:70:58:b8:7a
d9:60:7c:e6:d6:66:36:cb:62:4d:84:e8:b9:9e:62:2f
97:2c:cd:c2:28:6e:32:a5:c9:33:a1:8d:96:41:05:5c
84:7a:82:80:4e:df:8b:98:75:ba:84:84:a3:c7:9c:41
c8:19:d8:af:37:28:f2:e8:c2:dc:b9:b2:fa:7c:26:b6
31:bd:2f:e7:c3:aa:5d:c4:03:fb:db:42:3d:fe:2e:15
9e:23:40:b2:e5:e1:7e:a0:e1:5d:d7:ff:aa:ed:e9:09
99:55:2d:48:4c:67:95:b6:3f:d8:87:c7:b3:b6:ae:b0
45:42:9c:6a:3f:a7:8e:d2:79:e7:2e:51:d6:f1:25:e8
4f:4a:e4:cc:4e:2e:ae:d7:a6:b2:8a:d5:5d:76:42:ff
19:eb:0d:b9:a0:8c:76:ed:9d:e6:e9:83:0c:35:d7:d3
4c:16:2a:6b:25:1f:7a:f1:6d:6f:33:cf:21:8c:9f:a8
43:0b:11:d3:da:cf:ec:1e:64:54:c1:3e:12:13:f1:eb
b5:
public exponent:
01:00:01:
private exponent:
00:ab:ca:e1:64:fc:b3:8f:32:ad:ab:5f:16:39:c1:85
9a:1f:ce:3f:4b:a1:b4:3b:01:19:32:16:fa:a9:bd:9a
98:0f:5c:3a:59:2e:e5:f0:81:6e:cf:10:14:3c:f6:7a
68:b4:a7:2a:88:ae:53:b6:68:a7:54:c5:45:21:09:77
73:6b:3f:94:fd:59:e5:ef:a9:7b:06:76:02:38:1a:39
9b:9f:7e:6e:f9:2c:d0:7a:37:f6:3c:a7:f1:5b:c8:56
cd:57:89:56:f7:b3:16:5d:f8:43:87:6d:49:d9:77:09
b6:a9:5e:37:fc:58:78:e6:4a:f5:21:c2:e8:36:6f:5d
07:ed:d4:d8:3f:2a:da:a1:7a:92:16:50:11:9b:91:91
8e:49:40:48:d2:a7:9c:af:de:b4:86:59:a4:03:c6:2a