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
d6:f2:66:13:13:11:a8:7f:57:b1:2b:f2:7e:c6:fa:38
29:61:b2:eb:d2:78:ca:e7:d5:6f:d2:c3:6c:b8:1e:ff
26:fc:47:62:46:b6:03:c1:d9:a0:03:4f:16:5f:fe:65
e7:56:a2:21:38:b0:34:17:45:00:56:26:e8:5e:8b:fe
e0:fc:fc:a0:3e:2c:00:fb:c3:f8:dc:07:05:73:59:59
fd:b6:7a:c3:cd:4d:da:5a:1c:38:17:2e:15:8f:50:18
52:bb:d2:a9:fe:88:6f:d2:71:d3:7e:74:85:a3:88:ba
99:79:4c:ce:36:23:63:0a:17:16:00:da:fb:76:fb:90
bb:c1:08:77:43:0d:ce:81:a8:89:47:ea:bc:f0:40:65
07:0d:d0:9c:5e:02:b5:2b:89:cb:65:56:c3:24:b0:e1
39:30:20:aa:03:4c:20:7b:36:14:24:cb:8b:64:74:fc
00:2e:de:d6:d1:9b:93:17:d1:98:37:7f:6a:cd:42:d3
20:b2:92:2e:96:1e:3c:04:3e:99:1c:c7:03:d4:b9:87
98:e2:a5:41:b8:3f:0c:1a:94:54:16:4d:2b:83:58:de
01:
prime1:
00:f9:82:8d:c3:92:3d:bd:52:6b:ff:4e:0b:97:b3:8f
a2:56:53:49:05:1e:5b:20:cc:1b:fc:a4:0e:eb:ad:ce
1f:79:7a:3c:e6:b3:6d:1e:1a:bf:5a:01:76:c9:14:6c
0b:2d:6e:b2:56:55:b4:4d:f8:da:15:68:eb:07:59:43
c7:de:8b:01:14:8f:6d:3b:c5:d6:7d:86:f1:ba:9a:88
0c:bc:06:96:2a:94:59:d4:e1:eb:15:dd:20:9e:98:78
58:ca:ef:19:a7:52:c4:a0:35:91:d8:0b:3b:d2:93:aa
60:bc:44:c3:a5:ac:1c:f4:38:3e:60:79:da:c3:53:9a
6b:87:87:77:36:09:a1:27:b2:60:3e:aa:f3:73:d4:a7
af:f7:74:9e:c7:19:b1:e4:75:ed:79:56:67:53:05:1c
4e:55:f5:be:6a:9c:3a:4b:15:73:55:90:7c:a2:e9:45
07:d5:eb:a0:3f:da:d6:7f:ae:4b:62:6d:b5:8d:af:48
31:
prime2:
00:d3:32:5e:b2:ef:2e:bf:d1:57:85:28:bb:5c:37:c0
8a:ed:46:47:23:4e:22:72:2f:de:0a:a6:36:64:8f:9e
f5:67:a3:5d:d4:2d:2b:e2:3c:c7:b8:0c:79:37:bf:e6
67:96:84:4c:be:98:1f:86:e0:3d:c9:4a:b6:50:de:2f
90:61:25:74:49:ff:33:a7:93:3a:12:2b:c2:2c:38:29
9f:16:d8:20:16:77:e6:04:27:23:44:35:5a:a2:a1:72
40:91:b6:39:5f:e6:57:9f:59:6a:e4:5c:da:50:c7:4f
fe:df:1f:40:3c:e7:05:f6:f9:52:e9:c4:2c:e0:68:46
47:6e:52:76:bc:c1:19:7c:2d:50:2f:ef:53:e1:67:7c
b8:7e:84:72:6d:60:2c:ff:ff:7f:7a:ec:1e:54:8c:b1
a8:64:0b:54:f5:c3:4e:dd:dc:22:3b:54:05:7f:cc:32
64:d5:87:15:9f:f8:9f:07:83:4e:a0:d2:13:48:6b:be
c5:
coefficient:
2e:c4:04:3b:cf:3f:4a:dd:f0:32:e4:fc:93:fe:ef:b8
83:14:8e:a9:6b:ba:73:28:b6:b1:49:57:67:d7:ca:39
f0:da:23:99:df:3d:9b:8c:7b:c9:9a:fe:22:69:29:87
a3:ad:2a:6b:a7:5a:42:09:57:fd:8e:55:0a:1a:e9:36
02:e7:b3:47:82:41:c1:21:b2:8b:6a:35:30:60:17:c3
a5:3b:cc:2a:cb:e1:7a:23:14:6c:8a:87:29:49:8a:0a
c3:34:a8:aa:64:92:74:82:f1:01:3a:00:2e:bf:d6:d3
00:86:01:ee:84:83:42:8d:c7:b0:88:ac:62:12:9d:b5
18:28:76:13:34:24:92:fb:a9:57:e5:4c:8a:a9:bb:73
a5:02:64:9b:73:4c:18:6b:29:e4:ba:04:da:66:ef:b4
0c:46:fd:55:ff:62:5b:76:80:a9:13:29:c1:4b:43:51
0b:44:d3:01:9d:c5:e5:6b:ac:a0:e5:b9:0c:41:08:49
exp1:
00:9d:50:d4:63:4f:cc:7f:96:fd:22:de:a9:6e:7f:b7
7b:4a:64:7e:b0:ac:80:16:80:ba:d0:a2:fc:09:5a:ef
90:66:be:4c:b1:c4:c5:72:ea:b8:65:5e:70:ef:bd:61
95:f6:92:49:fd:27:52:64:ab:17:8d:d2:36:05:cf:21
6e:5e:81:54:30:0b:72:7a:f2:75:17:76:42:e9:3d:cf
b3:ff:c5:43:5f:1b:64:3c:56:29:2c:02:dd:33:41:bc
f7:77:14:24:1f:9c:8e:fe:d8:67:d9:48:d3:f2:24:4a
93:6d:81:09:be:66:73:67:04:23:48:1e:ec:70:a5:40
c2:b5:94:12:f4:ce:43:7e:cc:f7:e3:eb:53:4e:5f:f1
4e:80:7e:56:32:00:a0:6a:04:74:b3:41:68:2a:2f:19
b6:c6:7a:08:12:1d:e8:9f:38:aa:1c:73:da:31:b9:54
5d:e9:62:04:0a:de:c6:c9:80:32:65:9f:f8:8d:99:cb
b1:
exp2:
77:f1:8f:5e:c8:00:20:e8:5f:70:3c:a5:cb:c5:ce:10
18:99:65:25:63:e6:a4:3b:13:3b:b1:12:0b:22:96:00
81:8f:82:66:52:11:2e:37:9e:a5:a4:4f:e3:9d:94:d9
17:de:a3:47:4b:55:fc:5f:b6:37:6f:bb:03:4a:6c:70
ee:fb:3a:84:1b:ef:d6:28:03:3e:f6:a3:1c:41:f9:41
64:d3:f1:c5:50:ea:cd:48:fc:fa:6b:e2:c1:a2:37:24
fc:9c:25:11:95:dc:05:9f:de:d9:3a:f2:b2:15:c0:14
db:da:ff:bc:96:e8:08:4f:a9:0b:22:62:ea:3f:ce:4a
1b:19:b2:5d:98:bd:44:8c:e7:91:91:b6:6d:b9:3c:57
fb:62:75:2a:31:08:dd:8a:d9:77:92:49:d7:72:e0:22
e6:4e:99:3d:ca:62:2c:16:2d:4a:cc:79:23:0f:71:3f
5b:15:90:ee:7d:60:b3:ff:9a:d4:32:fa:c8:92:88:91