feat(hsm-emulator): PKCS#11 HSM emulator in Zig (M0–M7)

Software HSM that compiles to a real Cryptoki (PKCS#11) v2.40 shared
object, loadable by pkcs11-tool / OpenSSL like a smartcard. SoftHSM2-style
three-layer split: C-ABI facade -> typed core state -> crypto + persistence.

- M0  hand-written v2.40 ABI + build-time OASIS cross-check; exports only C_GetFunctionList
- M1  sessions, login, Argon2id PIN, lockout
- M2  objects + find, CKA_PRIVATE gating, persisted token store
- M3  RNG, SHA-2 digest, HMAC, AES-CBC/CBC-PAD/GCM
- M4  ECDSA P-256/P-384 keygen + sign/verify (std.crypto)
- M5  RSA 2048-4096 via libcrypto: PKCS#1 v1.5 / PSS sign-verify, PKCS#1 / OAEP enc-dec
- M6  encrypted-at-rest store: selective AES-256-GCM envelope, master key wrapped under Argon2id(User-PIN)
- M7  hardening (session-secret zeroization, fail-closed relock, heap-leak scrub, ubsan-free ABI) + Docker

Verified three ways: 65/65 unit tests, in-process dlopen smoke, and
cross-process OpenSC pkcs11-tool (token keys survive process restart).

Also excludes vendor/pkcs11 from the trailing-whitespace / end-of-file
pre-commit hooks so the vendored OASIS headers keep their documented
SHA-256 provenance.
This commit is contained in:
CarterPerez-dev 2026-06-01 20:35:48 -04:00
parent 9e7246e417
commit ccb1b40a15
45 changed files with 10760 additions and 2 deletions

View File

@ -406,9 +406,9 @@ repos:
- id: check-symlinks
exclude: (\.pnpm-store|node_modules)/
- id: end-of-file-fixer
exclude: (\.pnpm-store|node_modules)/
exclude: (\.pnpm-store|node_modules)/|^PROJECTS/advanced/hsm-emulator/vendor/
- id: trailing-whitespace
exclude: (\.pnpm-store|node_modules)/
exclude: (\.pnpm-store|node_modules)/|^PROJECTS/advanced/hsm-emulator/vendor/
- id: check-illegal-windows-names
exclude: (\.pnpm-store|node_modules)/
- id: check-executables-have-shebangs

View File

@ -0,0 +1,7 @@
# ©AngelaMos | 2026
# .dockerignore
.zig-cache
zig-out
docs
.git
*.tmp

View File

@ -0,0 +1,13 @@
# ©AngelaMos | 2026
# .gitignore
# Zig build artifacts and compilation cache
.zig-cache/
zig-out/
# Local-only development docs (handoffs, archives, Zig reference notes)
docs/
# HSM state files if ever written into the project dir
.angelamos-hsm-*
*.tmp

View File

@ -0,0 +1,40 @@
# ©AngelaMos | 2026
# Dockerfile
FROM debian:bookworm-slim AS builder
ARG ZIG_VERSION=0.16.0
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl xz-utils libssl-dev libc6-dev \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fSL "https://ziglang.org/download/${ZIG_VERSION}/zig-x86_64-linux-${ZIG_VERSION}.tar.xz" \
-o /tmp/zig.tar.xz \
&& mkdir -p /opt/zig \
&& tar -xJf /tmp/zig.tar.xz -C /opt/zig --strip-components=1 \
&& rm /tmp/zig.tar.xz
ENV PATH="/opt/zig:${PATH}"
WORKDIR /src
COPY build.zig build.zig.zon pkcs11.map ./
COPY src ./src
COPY vendor ./vendor
COPY examples ./examples
COPY tests ./tests
RUN zig build --release=safe
FROM debian:bookworm-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
opensc libssl3 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /hsm
COPY --from=builder /src/zig-out/lib/ /hsm/lib/
COPY docker/demo.sh /hsm/demo.sh
RUN chmod +x /hsm/demo.sh
ENV HSM_MODULE=/hsm/lib/libhsm.so.0.1.0 \
ANGELAMOS_HSM_TOKEN=/hsm/state/token \
ANGELAMOS_HSM_OBJECTS=/hsm/state/objects
ENTRYPOINT ["/hsm/demo.sh"]

View File

@ -0,0 +1,661 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

View File

@ -0,0 +1,160 @@
```
██╗ ██╗███████╗███╗ ███╗ ███████╗███╗ ███╗██╗ ██╗██╗ █████╗ ████████╗ ██████╗ ██████╗
██║ ██║██╔════╝████╗ ████║ ██╔════╝████╗ ████║██║ ██║██║ ██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗
███████║███████╗██╔████╔██║ █████╗ ██╔████╔██║██║ ██║██║ ███████║ ██║ ██║ ██║██████╔╝
██╔══██║╚════██║██║╚██╔╝██║ ██╔══╝ ██║╚██╔╝██║██║ ██║██║ ██╔══██║ ██║ ██║ ██║██╔══██╗
██║ ██║███████║██║ ╚═╝ ██║ ███████╗██║ ╚═╝ ██║╚██████╔╝███████╗██║ ██║ ██║ ╚██████╔╝██║ ██║
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
```
[![Cybersecurity Projects](https://img.shields.io/badge/Cybersecurity--Projects-Project%20%2333-red?style=flat&logo=github)](https://github.com/CarterPerez-dev/Cybersecurity-Projects/tree/main/PROJECTS/advanced/hsm-emulator)
[![Zig](https://img.shields.io/badge/Zig-0.16.0-F7A41D?style=flat&logo=zig&logoColor=white)](https://ziglang.org)
[![PKCS#11](https://img.shields.io/badge/PKCS%2311-v2.40-4B7BEC?style=flat)](https://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/pkcs11-base-v2.40-errata01-os.html)
[![Verified with](https://img.shields.io/badge/verified-pkcs11--tool-green?style=flat)](https://github.com/OpenSC/OpenSC)
[![License: AGPLv3](https://img.shields.io/badge/License-AGPL_v3-purple.svg)](https://www.gnu.org/licenses/agpl-3.0)
> A software **Hardware Security Module** that compiles to a real Cryptoki (PKCS#11) shared object. Load it with `pkcs11-tool`, OpenSSL, or any PKCS#11 host the same way you would a real smartcard or HSM — it speaks the C ABI byte-for-byte.
## Why PKCS#11 in Zig
PKCS#11 (Cryptoki) is the C-ABI standard that smartcards, YubiKeys, and cloud HSMs all speak. A conforming module is a `.so` that exports one function — `C_GetFunctionList` — returning a 68-entry table of function pointers in a *fixed canonical order*. Get one struct offset or one pointer slot wrong and the host loads garbage.
That makes it a perfect showcase for Zig's C interop: `extern struct` with natural alignment, `callconv(.c)`, a version script that exports exactly one symbol, and a hand-written ABI that is **machine-checked against the official OASIS headers at build time**.
## What Works Today (M0)
- Loads cleanly under OpenSC `pkcs11-tool` 0.26.1 — enumerates the slot and token (`-L`) and advertises 19 mechanisms (`-M`)
- Exports **only** `C_GetFunctionList` (verified with `objdump -T`)
- The full v2.40 ABI hand-written in `src/ck.zig`: every type, 200+ constants, every struct, and the 68-entry `CK_FUNCTION_LIST` in canonical order
- A build-time cross-check (`zig build test`) that translates the vendored OASIS headers and asserts `@sizeOf` / `@offsetOf` / constant equality **and per-function C-ABI signatures** against `ck.zig` — the spec compliance is a compile-time invariant, not a hope
- General + slot/token entry points implemented for real; session, object, crypto, key-management, and RNG entry points are typed stubs returning `CKR_FUNCTION_NOT_SUPPORTED` until their milestone lands
## Quick Start
```bash
git clone https://github.com/CarterPerez-dev/Cybersecurity-Projects.git
cd Cybersecurity-Projects/PROJECTS/advanced/hsm-emulator
./install.sh
```
`install.sh` checks for Zig 0.16, OpenSC, and OpenSSL, builds the module in ReleaseSafe, runs the ABI cross-check + smoke test, and confirms `pkcs11-tool` can load it. Then drive it like any real token:
```bash
pkcs11-tool --module zig-out/lib/libhsm.so -L # list slots and token
pkcs11-tool --module zig-out/lib/libhsm.so -M # list mechanisms
```
```
Available slots:
Slot 0 (0x0): AngelaMos HSM Emulator Slot 0
token state: uninitialized
```
> [!TIP]
> This project uses [`just`](https://github.com/casey/just) as a command runner. Type `just` to see everything. `just spy -L` wraps the module in `pkcs11-spy.so` and logs every Cryptoki call — the fastest way to watch the ABI work.
>
> Install: `curl -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin`
## Architecture
The same three-layer split SoftHSM2 uses: a thin C-ABI façade over typed core state over the store and crypto backends.
```
PKCS#11 host (pkcs11-tool, OpenSSL, p11-kit)
│ C ABI
┌───────────────────────────────────────────┐
│ C_GetFunctionList (src/main.zig) │ one exported symbol,
│ 68-entry CK_FUNCTION_LIST │ one version script
└───────────────────────┬─────────────────────┘
┌───────────────────────┴─────────────────────┐
│ ABI façade src/ck.zig + src/api/*.zig │ hand-written Cryptoki ABI
│ general · slot_token · session · object · │ + per-call entry points
│ crypto_ops · keymgmt · random │
└───────────────────────┬─────────────────────┘
┌───────────────────────┴─────────────────────┐
│ core state src/core/{state,lock}.zig │ global instance, init args,
│ │ C-boundary-safe locking
└───────────────────────┬─────────────────────┘
┌───────────────────────┴─────────────────────┐
│ store + crypto (built milestone by │ in-memory → encrypted file
│ milestone: sessions, objects, AES/EC/RSA) │ backend at rest
└───────────────────────────────────────────────┘
```
**Design decisions:** non-RSA crypto is pure-Zig `std.crypto`; RSA links libcrypto (OpenSSL EVP) since `std.crypto` has no public RSA. RNG is sourced from `getrandom(2)` directly (there is no `std.Io` at the C boundary, and `std.crypto.random` was removed in Zig 0.16). The ABI is structured for v2.40 with room to add the v3.0 `C_GetInterface` surface later.
## Build and Test
```bash
zig build # build the module → zig-out/lib/libhsm.so
zig build test # ABI cross-check vs OASIS headers + unit tests
zig build smoke # dlopen the built .so and exercise the ABI as a host would
just ci # fmt-check + test + smoke
```
The smoke harness in `examples/smoke.zig` is not a unit test — it `dlopen`s the *actual built shared object* and calls through the function list exactly like an external host, so it catches export and ABI-shape bugs that in-process tests cannot.
## Run in Docker
No Zig or OpenSC on the host? The container builds the module and drives it end-to-end through `pkcs11-tool` — token init, RSA + EC keygen and signing, AES-CBC round-trip — all inside the image.
```bash
just docker-demo # build the image, then run the full pkcs11-tool demo
```
Or with Docker directly:
```bash
docker build -t angelamos-hsm:latest .
docker run --rm angelamos-hsm:latest
```
A multi-stage build compiles the module in ReleaseSafe in a `debian-slim` builder, then ships only the `.so` plus `opensc` and `libssl3` in a ~96 MB runtime image. The demo exits non-zero if any signature fails to verify.
## Project Structure
```
hsm-emulator/
├── build.zig # addLibrary(.dynamic), version script, test + smoke steps, translate-c
├── build.zig.zon # package manifest
├── pkcs11.map # version script — exports only C_GetFunctionList
├── src/
│ ├── ck.zig # the hand-written Cryptoki v2.40 ABI (types, constants, structs, list)
│ ├── config.zig # identity strings, key-size bounds, mechanism list (no magic numbers)
│ ├── util.zig # comptime helpers (space-padded fixed fields)
│ ├── main.zig # exported C_GetFunctionList + the wired 68-slot table
│ ├── core/
│ │ ├── state.zig # global instance, init-args parsing, atomic init flag
│ │ └── lock.zig # spinlock wrapper (std.Thread.Mutex is gone in 0.16)
│ └── api/
│ ├── general.zig # C_Initialize / Finalize / GetInfo (locking template)
│ ├── slot_token.zig # slot + token + mechanism queries
│ └── session.zig, object.zig, crypto_ops.zig, keymgmt.zig, random.zig
├── tests/abi_test.zig # @sizeOf/@offsetOf/constant asserts, incl. cross-check vs OASIS
├── examples/smoke.zig # loads the built .so via dlopen and drives it
└── vendor/pkcs11/ # unmodified OASIS v2.40 headers (build-time cross-check only)
```
## Roadmap
Each milestone ends with a proof from a real external tool — no feature is "done" until `pkcs11-tool` or OpenSSL exercises it.
| Milestone | Scope | Proof |
|-----------|-------|-------|
| **M0** ✅ | Scaffold + hand-written ABI + loadable `.so` | `pkcs11-tool -L/-M`, `objdump -T` |
| **M1** | Sessions + login + PIN (Argon2id, lockout) | `pkcs11-tool --init-token --init-pin --login --change-pin` |
| **M2** | Objects + find (in-memory), `CKA_PRIVATE` gating | `pkcs11-tool -O --read-object` |
| **M3** | RNG + SHA + HMAC + AES-GCM/CBC | `--hash --encrypt --decrypt --generate-random` |
| **M4** | ECDSA P-256/384 + keygen | `--keypairgen EC --sign`, cross-verify with OpenSSL |
| **M5** | RSA via libcrypto (v1.5 / PSS / OAEP) | OpenSSL pkcs11 provider signs through the module |
| **M6** | Encrypted file backend at rest (AES-256-GCM under Argon2id KEK) | persist across restart; tamper → fails closed |
| **M7** | Hardening (secret zeroization, fail-closed) + Docker | — |
| **M8** | Learn modules + mechanism reference + final docs | — |
## License
[AGPL 3.0](LICENSE)

View File

@ -0,0 +1,84 @@
// ©AngelaMos | 2026
// build.zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSafe });
const lib = b.addLibrary(.{
.name = "hsm",
.linkage = .dynamic,
.version = .{ .major = 0, .minor = 1, .patch = 0 },
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.sanitize_c = .trap,
}),
});
lib.root_module.linkSystemLibrary("crypto", .{});
lib.setVersionScript(b.path("pkcs11.map"));
b.installArtifact(lib);
const ck_module = b.createModule(.{
.root_source_file = b.path("src/ck.zig"),
.target = target,
.optimize = optimize,
});
const translate_c = b.addTranslateC(.{
.root_source_file = b.path("vendor/pkcs11/shim.h"),
.target = target,
.optimize = optimize,
});
translate_c.addIncludePath(b.path("vendor/pkcs11"));
const p11c_module = translate_c.createModule();
const tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("tests/abi_test.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "ck", .module = ck_module },
.{ .name = "p11c", .module = p11c_module },
},
}),
});
const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run ABI and unit tests");
test_step.dependOn(&run_tests.step);
const unit_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/test_all.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
unit_tests.root_module.linkSystemLibrary("crypto", .{});
const run_unit_tests = b.addRunArtifact(unit_tests);
test_step.dependOn(&run_unit_tests.step);
const smoke = b.addExecutable(.{
.name = "smoke",
.root_module = b.createModule(.{
.root_source_file = b.path("examples/smoke.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
.imports = &.{
.{ .name = "ck", .module = ck_module },
},
}),
});
const run_smoke = b.addRunArtifact(smoke);
run_smoke.step.dependOn(b.getInstallStep());
if (b.args) |args| run_smoke.addArgs(args);
const smoke_step = b.step("smoke", "Load the built .so via dlopen and exercise the Cryptoki ABI");
smoke_step.dependOn(&run_smoke.step);
}

View File

@ -0,0 +1,16 @@
// ©AngelaMos | 2026
// build.zig.zon
.{
.name = .hsm_emulator,
.version = "0.1.0",
.fingerprint = 0x42f25b5af31e3d64,
.minimum_zig_version = "0.16.0",
.dependencies = .{},
.paths = .{
"build.zig",
"build.zig.zon",
"pkcs11.map",
"src",
"vendor",
},
}

View File

@ -0,0 +1,49 @@
#!/usr/bin/env bash
# ©AngelaMos | 2026
# demo.sh
set -euo pipefail
MOD="${HSM_MODULE:-/hsm/lib/libhsm.so.0.1.0}"
SO_PIN=12345678
PIN=1234
WORK="$(mktemp -d)"
mkdir -p "$(dirname "$ANGELAMOS_HSM_TOKEN")" "$(dirname "$ANGELAMOS_HSM_OBJECTS")"
line() { printf '\n\033[1m== %s ==\033[0m\n' "$1"; }
line "AngelaMos HSM — PKCS#11 module loaded by OpenSC pkcs11-tool"
pkcs11-tool --module "$MOD" --init-token --label angelamos --so-pin "$SO_PIN" >/dev/null
pkcs11-tool --module "$MOD" --init-pin --so-pin "$SO_PIN" --pin "$PIN" >/dev/null
pkcs11-tool --module "$MOD" -I
line "Supported mechanisms"
pkcs11-tool --module "$MOD" -M
line "RSA-2048 — generate, sign (SHA256-RSA-PKCS), verify"
pkcs11-tool --module "$MOD" --login --pin "$PIN" --keypairgen --key-type RSA:2048 --label rsa --id 01 2>/dev/null
printf 'invoice #42 total $1000' > "$WORK/msg"
pkcs11-tool --module "$MOD" --login --pin "$PIN" --sign --mechanism SHA256-RSA-PKCS --id 01 -i "$WORK/msg" -o "$WORK/rsa.sig" 2>/dev/null
pkcs11-tool --module "$MOD" --login --pin "$PIN" --verify --mechanism SHA256-RSA-PKCS --id 01 -i "$WORK/msg" --signature-file "$WORK/rsa.sig"
line "EC P-256 — generate, sign (ECDSA-SHA256), verify"
pkcs11-tool --module "$MOD" --login --pin "$PIN" --keypairgen --key-type EC:prime256v1 --label ec --id 02 2>/dev/null
pkcs11-tool --module "$MOD" --login --pin "$PIN" --sign --mechanism ECDSA-SHA256 --id 02 -i "$WORK/msg" -o "$WORK/ec.sig" 2>/dev/null
pkcs11-tool --module "$MOD" --login --pin "$PIN" --verify --mechanism ECDSA-SHA256 --id 02 -i "$WORK/msg" --signature-file "$WORK/ec.sig"
line "AES-256 — generate, encrypt (AES-CBC), decrypt, compare"
pkcs11-tool --module "$MOD" --login --pin "$PIN" --keygen --key-type AES:32 --label aes --id 03 2>/dev/null
printf '0123456789ABCDEF' > "$WORK/pt"
IV=000102030405060708090a0b0c0d0e0f
pkcs11-tool --module "$MOD" --login --pin "$PIN" --encrypt --mechanism AES-CBC --id 03 --iv "$IV" -i "$WORK/pt" -o "$WORK/ct" 2>/dev/null
pkcs11-tool --module "$MOD" --login --pin "$PIN" --decrypt --mechanism AES-CBC --id 03 --iv "$IV" -i "$WORK/ct" -o "$WORK/dec" 2>/dev/null
if cmp -s "$WORK/pt" "$WORK/dec"; then
echo "AES-CBC round-trip: plaintext recovered OK"
else
echo "AES-CBC round-trip: MISMATCH" && exit 1
fi
line "Token objects (private material sealed at rest under Argon2id(User-PIN))"
pkcs11-tool --module "$MOD" --login --pin "$PIN" -O
line "Demo complete — RSA + ECDSA signatures verified, AES-CBC round-trip OK"

View File

@ -0,0 +1,382 @@
// ©AngelaMos | 2026
// smoke.zig
const std = @import("std");
const ck = @import("ck");
const GetFunctionList = *const fn (*?*ck.CK_FUNCTION_LIST) callconv(.c) ck.CK_RV;
const default_module = "zig-out/lib/libhsm.so";
const smoke_token = "/tmp/angelamos-hsm-smoke-token.bin";
const smoke_objects = "/tmp/angelamos-hsm-smoke-objects.bin";
extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int;
pub fn main() !void {
_ = setenv("ANGELAMOS_HSM_TOKEN", smoke_token, 1);
_ = setenv("ANGELAMOS_HSM_OBJECTS", smoke_objects, 1);
_ = std.c.unlink(smoke_token);
_ = std.c.unlink(smoke_objects);
defer _ = std.c.unlink(smoke_token);
defer _ = std.c.unlink(smoke_objects);
var so_pin = "12345678".*;
var user_pin = "1234".*;
var new_user_pin = "5678".*;
var wrong_pin = "0000".*;
var label: [32]u8 = @splat(' ');
@memcpy(label[0..11], "smoke-token");
var lib = try std.DynLib.open(default_module);
defer lib.close();
const getFunctionList = lib.lookup(GetFunctionList, "C_GetFunctionList") orelse {
std.debug.print("smoke: C_GetFunctionList not exported\n", .{});
return error.SymbolNotFound;
};
var list_ptr: ?*ck.CK_FUNCTION_LIST = null;
try check("C_GetFunctionList", getFunctionList(&list_ptr));
const f = list_ptr orelse return error.NullFunctionList;
if (f.version.major != 2 or f.version.minor != 40) return error.UnexpectedVersion;
try check("C_Initialize", f.C_Initialize.?(null));
if (f.C_Initialize.?(null) != ck.CKR_CRYPTOKI_ALREADY_INITIALIZED) return error.DoubleInitNotRejected;
var info: ck.CK_INFO = undefined;
try check("C_GetInfo", f.C_GetInfo.?(&info));
var count: ck.CK_ULONG = 0;
try check("C_GetSlotList(size)", f.C_GetSlotList.?(ck.CK_FALSE, null, &count));
if (count != 1) return error.UnexpectedSlotCount;
var slots: [4]ck.CK_SLOT_ID = undefined;
try check("C_GetSlotList(fill)", f.C_GetSlotList.?(ck.CK_FALSE, &slots, &count));
const slot = slots[0];
var slot_info: ck.CK_SLOT_INFO = undefined;
try check("C_GetSlotInfo", f.C_GetSlotInfo.?(slot, &slot_info));
var token_info: ck.CK_TOKEN_INFO = undefined;
try check("C_GetTokenInfo", f.C_GetTokenInfo.?(slot, &token_info));
if (token_info.flags & ck.CKF_TOKEN_INITIALIZED != 0) return error.TokenShouldStartUninitialized;
var mech_count: ck.CK_ULONG = 0;
try check("C_GetMechanismList(size)", f.C_GetMechanismList.?(slot, null, &mech_count));
if (mech_count == 0) return error.NoMechanisms;
try check("C_InitToken", f.C_InitToken.?(slot, &so_pin, so_pin.len, &label));
try check("C_GetTokenInfo(post-init)", f.C_GetTokenInfo.?(slot, &token_info));
if (token_info.flags & ck.CKF_TOKEN_INITIALIZED == 0) return error.InitTokenDidNotInitialize;
var h: ck.CK_SESSION_HANDLE = 0;
try check("C_OpenSession", f.C_OpenSession.?(slot, ck.CKF_SERIAL_SESSION | ck.CKF_RW_SESSION, null, null, &h));
var si: ck.CK_SESSION_INFO = undefined;
try check("C_GetSessionInfo", f.C_GetSessionInfo.?(h, &si));
if (si.state != ck.CKS_RW_PUBLIC_SESSION) return error.UnexpectedPublicState;
try check("C_Login(SO)", f.C_Login.?(h, ck.CKU_SO, &so_pin, so_pin.len));
try check("C_GetSessionInfo(SO)", f.C_GetSessionInfo.?(h, &si));
if (si.state != ck.CKS_RW_SO_FUNCTIONS) return error.UnexpectedSoState;
try check("C_InitPIN", f.C_InitPIN.?(h, &user_pin, user_pin.len));
try check("C_Logout(SO)", f.C_Logout.?(h));
try check("C_GetTokenInfo(post-initpin)", f.C_GetTokenInfo.?(slot, &token_info));
if (token_info.flags & ck.CKF_USER_PIN_INITIALIZED == 0) return error.UserPinNotInitialized;
try check("C_Login(USER)", f.C_Login.?(h, ck.CKU_USER, &user_pin, user_pin.len));
try check("C_GetSessionInfo(USER)", f.C_GetSessionInfo.?(h, &si));
if (si.state != ck.CKS_RW_USER_FUNCTIONS) return error.UnexpectedUserState;
try check("C_SetPIN", f.C_SetPIN.?(h, &user_pin, user_pin.len, &new_user_pin, new_user_pin.len));
try check("C_Logout(USER)", f.C_Logout.?(h));
try check("C_Login(USER,new)", f.C_Login.?(h, ck.CKU_USER, &new_user_pin, new_user_pin.len));
var class_data: ck.CK_OBJECT_CLASS = ck.CKO_DATA;
var ck_true: ck.CK_BBOOL = ck.CK_TRUE;
var data_label = "smoke-data".*;
var data_value = "hello-hsm".*;
var create_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_CLASS, .pValue = &class_data, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) },
.{ .type = ck.CKA_LABEL, .pValue = &data_label, .ulValueLen = data_label.len },
.{ .type = ck.CKA_VALUE, .pValue = &data_value, .ulValueLen = data_value.len },
};
var h_data: ck.CK_OBJECT_HANDLE = 0;
try check("C_CreateObject(data)", f.C_CreateObject.?(h, &create_tmpl, create_tmpl.len, &h_data));
if (h_data == ck.CK_INVALID_HANDLE) return error.BadObjectHandle;
var priv_label = "smoke-secret".*;
var priv_value = "top-secret".*;
var priv_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_CLASS, .pValue = &class_data, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) },
.{ .type = ck.CKA_PRIVATE, .pValue = &ck_true, .ulValueLen = 1 },
.{ .type = ck.CKA_LABEL, .pValue = &priv_label, .ulValueLen = priv_label.len },
.{ .type = ck.CKA_VALUE, .pValue = &priv_value, .ulValueLen = priv_value.len },
};
var h_priv: ck.CK_OBJECT_HANDLE = 0;
try check("C_CreateObject(private)", f.C_CreateObject.?(h, &priv_tmpl, priv_tmpl.len, &h_priv));
var find_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_CLASS, .pValue = &class_data, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) },
};
var found: [8]ck.CK_OBJECT_HANDLE = undefined;
var nfound: ck.CK_ULONG = 0;
try check("C_FindObjectsInit", f.C_FindObjectsInit.?(h, &find_tmpl, find_tmpl.len));
try check("C_FindObjects", f.C_FindObjects.?(h, &found, found.len, &nfound));
try check("C_FindObjectsFinal", f.C_FindObjectsFinal.?(h));
if (nfound != 2) return error.FindCountWrong;
var probe = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_VALUE, .pValue = null, .ulValueLen = 0 },
};
try check("C_GetAttributeValue(len)", f.C_GetAttributeValue.?(h, h_data, &probe, probe.len));
if (probe[0].ulValueLen != data_value.len) return error.LenProbeWrong;
var valbuf: [64]u8 = undefined;
probe[0].pValue = &valbuf;
try check("C_GetAttributeValue(fetch)", f.C_GetAttributeValue.?(h, h_data, &probe, probe.len));
if (!std.mem.eql(u8, valbuf[0..probe[0].ulValueLen], &data_value)) return error.ValueMismatch;
var new_label = "relabeled!!".*;
var set_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_LABEL, .pValue = &new_label, .ulValueLen = new_label.len },
};
try check("C_SetAttributeValue", f.C_SetAttributeValue.?(h, h_data, &set_tmpl, set_tmpl.len));
var lblbuf: [32]u8 = undefined;
var lblq = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_LABEL, .pValue = &lblbuf, .ulValueLen = lblbuf.len },
};
try check("C_GetAttributeValue(label)", f.C_GetAttributeValue.?(h, h_data, &lblq, lblq.len));
if (!std.mem.eql(u8, lblbuf[0..lblq[0].ulValueLen], &new_label)) return error.RelabelFailed;
var osize: ck.CK_ULONG = 0;
try check("C_GetObjectSize", f.C_GetObjectSize.?(h, h_data, &osize));
if (osize == 0) return error.ZeroObjectSize;
try check("C_DestroyObject", f.C_DestroyObject.?(h, h_data));
if (f.C_FindObjects.?(h, &found, found.len, &nfound) != ck.CKR_OPERATION_NOT_INITIALIZED) return error.FsmNotEnforced;
try check("C_Logout(after-objects)", f.C_Logout.?(h));
try check("C_FindObjectsInit(public)", f.C_FindObjectsInit.?(h, null, 0));
try check("C_FindObjects(public)", f.C_FindObjects.?(h, &found, found.len, &nfound));
try check("C_FindObjectsFinal(public)", f.C_FindObjectsFinal.?(h));
if (nfound != 0) return error.PrivateObjectLeaked;
if (f.C_GetAttributeValue.?(h, h_priv, &lblq, lblq.len) != ck.CKR_OBJECT_HANDLE_INVALID) return error.PrivateNotGated;
var ck_false: ck.CK_BBOOL = ck.CK_FALSE;
var undead_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_CLASS, .pValue = &class_data, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) },
.{ .type = ck.CKA_DESTROYABLE, .pValue = &ck_false, .ulValueLen = 1 },
};
var h_undead: ck.CK_OBJECT_HANDLE = 0;
try check("C_CreateObject(undestroyable)", f.C_CreateObject.?(h, &undead_tmpl, undead_tmpl.len, &h_undead));
if (f.C_DestroyObject.?(h, h_undead) != ck.CKR_ACTION_PROHIBITED) return error.DestroyableGateBroken;
var immut_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_CLASS, .pValue = &class_data, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) },
.{ .type = ck.CKA_MODIFIABLE, .pValue = &ck_false, .ulValueLen = 1 },
};
var h_immut: ck.CK_OBJECT_HANDLE = 0;
try check("C_CreateObject(immutable)", f.C_CreateObject.?(h, &immut_tmpl, immut_tmpl.len, &h_immut));
var nope = "nope".*;
var set_immut = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_LABEL, .pValue = &nope, .ulValueLen = nope.len },
};
if (f.C_SetAttributeValue.?(h, h_immut, &set_immut, set_immut.len) != ck.CKR_ACTION_PROHIBITED) return error.ModifiableGateBroken;
if (f.C_FindObjectsInit.?(h, null, 3) != ck.CKR_ARGUMENTS_BAD) return error.ArgsBadNotEnforced;
var attempt: u8 = 0;
while (attempt < 3) : (attempt += 1) {
if (f.C_Login.?(h, ck.CKU_USER, &wrong_pin, wrong_pin.len) != ck.CKR_PIN_INCORRECT) return error.WrongPinNotRejected;
}
if (f.C_Login.?(h, ck.CKU_USER, &new_user_pin, new_user_pin.len) != ck.CKR_PIN_LOCKED) return error.LockoutNotEnforced;
try check("C_GetTokenInfo(locked)", f.C_GetTokenInfo.?(slot, &token_info));
if (token_info.flags & ck.CKF_USER_PIN_LOCKED == 0) return error.LockFlagNotSet;
var sha_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_SHA256, .pParameter = null, .ulParameterLen = 0 };
try check("C_DigestInit", f.C_DigestInit.?(h, &sha_mech));
var abc = "abc".*;
var dg: [64]u8 = undefined;
var dglen: ck.CK_ULONG = dg.len;
try check("C_Digest", f.C_Digest.?(h, &abc, abc.len, &dg, &dglen));
const sha_abc = [_]u8{
0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
};
if (dglen != 32 or !std.mem.eql(u8, dg[0..32], &sha_abc)) return error.DigestVectorMismatch;
var class_secret: ck.CK_OBJECT_CLASS = ck.CKO_SECRET_KEY;
var ck_yes: ck.CK_BBOOL = ck.CK_TRUE;
var kt_generic: ck.CK_KEY_TYPE = ck.CKK_GENERIC_SECRET;
var hkey_val = "secret-hmac-key".*;
var hmac_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_CLASS, .pValue = &class_secret, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) },
.{ .type = ck.CKA_KEY_TYPE, .pValue = &kt_generic, .ulValueLen = @sizeOf(ck.CK_KEY_TYPE) },
.{ .type = ck.CKA_VALUE, .pValue = &hkey_val, .ulValueLen = hkey_val.len },
.{ .type = ck.CKA_SIGN, .pValue = &ck_yes, .ulValueLen = 1 },
.{ .type = ck.CKA_VERIFY, .pValue = &ck_yes, .ulValueLen = 1 },
};
var h_hmac: ck.CK_OBJECT_HANDLE = 0;
try check("C_CreateObject(hmac key)", f.C_CreateObject.?(h, &hmac_tmpl, hmac_tmpl.len, &h_hmac));
var hmac_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_SHA256_HMAC, .pParameter = null, .ulParameterLen = 0 };
var hmsg = "authenticate me".*;
var sig: [64]u8 = undefined;
var siglen: ck.CK_ULONG = sig.len;
try check("C_SignInit", f.C_SignInit.?(h, &hmac_mech, h_hmac));
try check("C_Sign", f.C_Sign.?(h, &hmsg, hmsg.len, &sig, &siglen));
if (siglen != 32) return error.HmacLenWrong;
try check("C_VerifyInit", f.C_VerifyInit.?(h, &hmac_mech, h_hmac));
try check("C_Verify", f.C_Verify.?(h, &hmsg, hmsg.len, &sig, siglen));
try check("C_VerifyInit(tamper)", f.C_VerifyInit.?(h, &hmac_mech, h_hmac));
sig[0] ^= 0xff;
if (f.C_Verify.?(h, &hmsg, hmsg.len, &sig, siglen) != ck.CKR_SIGNATURE_INVALID) return error.HmacTamperNotDetected;
var kt_aes: ck.CK_KEY_TYPE = ck.CKK_AES;
var aes_val = [_]u8{0} ** 32;
for (0..32) |j| aes_val[j] = @intCast(j);
var aes_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_CLASS, .pValue = &class_secret, .ulValueLen = @sizeOf(ck.CK_OBJECT_CLASS) },
.{ .type = ck.CKA_KEY_TYPE, .pValue = &kt_aes, .ulValueLen = @sizeOf(ck.CK_KEY_TYPE) },
.{ .type = ck.CKA_VALUE, .pValue = &aes_val, .ulValueLen = aes_val.len },
.{ .type = ck.CKA_ENCRYPT, .pValue = &ck_yes, .ulValueLen = 1 },
.{ .type = ck.CKA_DECRYPT, .pValue = &ck_yes, .ulValueLen = 1 },
};
var h_aes: ck.CK_OBJECT_HANDLE = 0;
try check("C_CreateObject(aes key)", f.C_CreateObject.?(h, &aes_tmpl, aes_tmpl.len, &h_aes));
var iv = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
var cbc_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_AES_CBC_PAD, .pParameter = &iv, .ulParameterLen = iv.len };
var aes_pt = "AES round-trip through the Cryptoki ABI".*;
var aes_ct: [64]u8 = undefined;
var ctlen: ck.CK_ULONG = aes_ct.len;
try check("C_EncryptInit", f.C_EncryptInit.?(h, &cbc_mech, h_aes));
try check("C_Encrypt", f.C_Encrypt.?(h, &aes_pt, aes_pt.len, &aes_ct, &ctlen));
var aes_back: [64]u8 = undefined;
var backlen: ck.CK_ULONG = aes_back.len;
try check("C_DecryptInit", f.C_DecryptInit.?(h, &cbc_mech, h_aes));
try check("C_Decrypt", f.C_Decrypt.?(h, &aes_ct, ctlen, &aes_back, &backlen));
if (backlen != aes_pt.len or !std.mem.eql(u8, aes_back[0..backlen], &aes_pt)) return error.AesRoundTripFailed;
var gen_keylen: ck.CK_ULONG = 32;
var gen_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_VALUE_LEN, .pValue = &gen_keylen, .ulValueLen = @sizeOf(ck.CK_ULONG) },
};
var gen_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_AES_KEY_GEN, .pParameter = null, .ulParameterLen = 0 };
var h_gen: ck.CK_OBJECT_HANDLE = 0;
try check("C_GenerateKey", f.C_GenerateKey.?(h, &gen_mech, &gen_tmpl, gen_tmpl.len, &h_gen));
var genval_q = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_VALUE, .pValue = null, .ulValueLen = 0 },
};
if (f.C_GetAttributeValue.?(h, h_gen, &genval_q, genval_q.len) != ck.CKR_ATTRIBUTE_SENSITIVE) return error.GeneratedKeyNotSensitive;
var ec_params = [_]u8{ 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 };
var ec_kpgen = ck.CK_MECHANISM{ .mechanism = ck.CKM_EC_KEY_PAIR_GEN, .pParameter = null, .ulParameterLen = 0 };
var ecpub_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_EC_PARAMS, .pValue = &ec_params, .ulValueLen = ec_params.len },
.{ .type = ck.CKA_VERIFY, .pValue = &ck_yes, .ulValueLen = 1 },
};
var ecpriv_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_SIGN, .pValue = &ck_yes, .ulValueLen = 1 },
.{ .type = ck.CKA_PRIVATE, .pValue = &ck_false, .ulValueLen = 1 },
};
var h_ecpub: ck.CK_OBJECT_HANDLE = 0;
var h_ecpriv: ck.CK_OBJECT_HANDLE = 0;
try check("C_GenerateKeyPair(EC)", f.C_GenerateKeyPair.?(h, &ec_kpgen, &ecpub_tmpl, ecpub_tmpl.len, &ecpriv_tmpl, ecpriv_tmpl.len, &h_ecpub, &h_ecpriv));
var ecdsa_mech = ck.CK_MECHANISM{ .mechanism = ck.CKM_ECDSA_SHA256, .pParameter = null, .ulParameterLen = 0 };
var ecmsg = "sign me over ECDSA P-256".*;
var ecsig: [128]u8 = undefined;
var ecsiglen: ck.CK_ULONG = ecsig.len;
try check("C_SignInit(ECDSA)", f.C_SignInit.?(h, &ecdsa_mech, h_ecpriv));
try check("C_Sign(ECDSA)", f.C_Sign.?(h, &ecmsg, ecmsg.len, &ecsig, &ecsiglen));
if (ecsiglen != 64) return error.EcdsaSigLenWrong;
try check("C_VerifyInit(ECDSA)", f.C_VerifyInit.?(h, &ecdsa_mech, h_ecpub));
try check("C_Verify(ECDSA)", f.C_Verify.?(h, &ecmsg, ecmsg.len, &ecsig, ecsiglen));
try check("C_VerifyInit(ECDSA tamper)", f.C_VerifyInit.?(h, &ecdsa_mech, h_ecpub));
ecsig[0] ^= 0xff;
if (f.C_Verify.?(h, &ecmsg, ecmsg.len, &ecsig, ecsiglen) != ck.CKR_SIGNATURE_INVALID) return error.EcdsaTamperNotDetected;
var ecval_q = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_VALUE, .pValue = null, .ulValueLen = 0 },
};
if (f.C_GetAttributeValue.?(h, h_ecpriv, &ecval_q, ecval_q.len) != ck.CKR_ATTRIBUTE_SENSITIVE) return error.EcPrivNotSensitive;
var ecpt_q = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_EC_POINT, .pValue = null, .ulValueLen = 0 },
};
try check("C_GetAttributeValue(EC_POINT)", f.C_GetAttributeValue.?(h, h_ecpub, &ecpt_q, ecpt_q.len));
if (ecpt_q[0].ulValueLen != 67) return error.EcPointLenWrong;
var rsa_bits: ck.CK_ULONG = 2048;
var rsa_kpgen = ck.CK_MECHANISM{ .mechanism = ck.CKM_RSA_PKCS_KEY_PAIR_GEN, .pParameter = null, .ulParameterLen = 0 };
var rsapub_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_MODULUS_BITS, .pValue = &rsa_bits, .ulValueLen = @sizeOf(ck.CK_ULONG) },
.{ .type = ck.CKA_VERIFY, .pValue = &ck_yes, .ulValueLen = 1 },
.{ .type = ck.CKA_ENCRYPT, .pValue = &ck_yes, .ulValueLen = 1 },
};
var rsapriv_tmpl = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_SIGN, .pValue = &ck_yes, .ulValueLen = 1 },
.{ .type = ck.CKA_DECRYPT, .pValue = &ck_yes, .ulValueLen = 1 },
.{ .type = ck.CKA_PRIVATE, .pValue = &ck_false, .ulValueLen = 1 },
};
var h_rsapub: ck.CK_OBJECT_HANDLE = 0;
var h_rsapriv: ck.CK_OBJECT_HANDLE = 0;
try check("C_GenerateKeyPair(RSA)", f.C_GenerateKeyPair.?(h, &rsa_kpgen, &rsapub_tmpl, rsapub_tmpl.len, &rsapriv_tmpl, rsapriv_tmpl.len, &h_rsapub, &h_rsapriv));
var rsa_sha_pkcs = ck.CK_MECHANISM{ .mechanism = ck.CKM_SHA256_RSA_PKCS, .pParameter = null, .ulParameterLen = 0 };
var rsamsg = "sign me over RSA PKCS#1 v1.5".*;
var rsasig: [256]u8 = undefined;
var rsasiglen: ck.CK_ULONG = rsasig.len;
try check("C_SignInit(RSA)", f.C_SignInit.?(h, &rsa_sha_pkcs, h_rsapriv));
try check("C_Sign(RSA)", f.C_Sign.?(h, &rsamsg, rsamsg.len, &rsasig, &rsasiglen));
if (rsasiglen != 256) return error.RsaSigLenWrong;
try check("C_VerifyInit(RSA)", f.C_VerifyInit.?(h, &rsa_sha_pkcs, h_rsapub));
try check("C_Verify(RSA)", f.C_Verify.?(h, &rsamsg, rsamsg.len, &rsasig, rsasiglen));
try check("C_VerifyInit(RSA tamper)", f.C_VerifyInit.?(h, &rsa_sha_pkcs, h_rsapub));
rsasig[10] ^= 0xff;
if (f.C_Verify.?(h, &rsamsg, rsamsg.len, &rsasig, rsasiglen) != ck.CKR_SIGNATURE_INVALID) return error.RsaTamperNotDetected;
var rsa_pkcs = ck.CK_MECHANISM{ .mechanism = ck.CKM_RSA_PKCS, .pParameter = null, .ulParameterLen = 0 };
var rsapt = "rsa secret".*;
var rsact: [256]u8 = undefined;
var rsactlen: ck.CK_ULONG = rsact.len;
try check("C_EncryptInit(RSA)", f.C_EncryptInit.?(h, &rsa_pkcs, h_rsapub));
try check("C_Encrypt(RSA)", f.C_Encrypt.?(h, &rsapt, rsapt.len, &rsact, &rsactlen));
if (rsactlen != 256) return error.RsaCtLenWrong;
var rsaback: [256]u8 = undefined;
var rsabacklen: ck.CK_ULONG = rsaback.len;
try check("C_DecryptInit(RSA)", f.C_DecryptInit.?(h, &rsa_pkcs, h_rsapriv));
try check("C_Decrypt(RSA)", f.C_Decrypt.?(h, &rsact, rsactlen, &rsaback, &rsabacklen));
if (rsabacklen != rsapt.len or !std.mem.eql(u8, rsaback[0..rsabacklen], &rsapt)) return error.RsaRoundTripFailed;
var rsaval_q = [_]ck.CK_ATTRIBUTE{
.{ .type = ck.CKA_PRIVATE_EXPONENT, .pValue = null, .ulValueLen = 0 },
};
if (f.C_GetAttributeValue.?(h, h_rsapriv, &rsaval_q, rsaval_q.len) != ck.CKR_ATTRIBUTE_SENSITIVE) return error.RsaPrivNotSensitive;
try check("C_CloseSession", f.C_CloseSession.?(h));
try check("C_Finalize", f.C_Finalize.?(null));
std.debug.print("smoke: OK\n", .{});
std.debug.print(" cryptokiVersion = {d}.{d}\n", .{ info.cryptokiVersion.major, info.cryptokiVersion.minor });
std.debug.print(" slots = {d}\n", .{count});
std.debug.print(" token label = {s}\n", .{token_info.label});
std.debug.print(" mechanisms = {d}\n", .{mech_count});
std.debug.print(" login + PIN = init/login/initpin/setpin OK; lockout trips after 3 wrong\n", .{});
std.debug.print(" objects = create/find/get(2-call)/set/size/destroy OK; CKA_PRIVATE hidden after logout\n", .{});
std.debug.print(" object gates = CKA_DESTROYABLE/CKA_MODIFIABLE=false enforced; FindObjectsInit arg-check OK\n", .{});
std.debug.print(" crypto = SHA-256 vector OK; HMAC sign/verify (+tamper) OK; AES-CBC-PAD round-trip OK\n", .{});
std.debug.print(" keygen = C_GenerateKey AES OK; generated key CKA_VALUE is sensitive (unextractable)\n", .{});
std.debug.print(" ecdsa = C_GenerateKeyPair EC P-256 OK; ECDSA-SHA256 sign/verify (+tamper) OK; priv scalar sensitive\n", .{});
std.debug.print(" rsa = C_GenerateKeyPair RSA-2048 OK; SHA256-RSA-PKCS sign/verify (+tamper) + RSA-PKCS enc/dec OK; priv sensitive\n", .{});
}
fn check(name: []const u8, rv: ck.CK_RV) !void {
if (rv != ck.CKR_OK) {
std.debug.print("smoke: {s} -> 0x{X}\n", .{ name, rv });
return error.CryptokiError;
}
}

View File

@ -0,0 +1,141 @@
#!/usr/bin/env bash
# ©AngelaMos | 2026
# install.sh
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
DIM='\033[2m'
NC='\033[0m'
info() { printf "${CYAN}${NC} %s\n" "$1"; }
ok() { printf "${GREEN}${NC} %s\n" "$1"; }
warn() { printf "${YELLOW}!${NC} %s\n" "$1"; }
fail() { printf "${RED}${NC} %s\n" "$1"; exit 1; }
MIN_ZIG="0.16.0"
banner() {
printf "\n"
printf "${CYAN}"
cat <<'EOF'
██╗ ██╗███████╗███╗ ███╗
██║ ██║██╔════╝████╗ ████║
███████║███████╗██╔████╔██║
██╔══██║╚════██║██║╚██╔╝██║
██║ ██║███████║██║ ╚═╝ ██║
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
EOF
printf "${NC}"
printf " ${DIM}hsm emulator installer — pkcs#11 v2.40 module in zig${NC}\n"
printf "\n"
}
check_zig() {
if ! command -v zig &>/dev/null; then
fail "Zig is not installed. Get 0.16.0 at https://ziglang.org/download/"
fi
local ver
ver=$(zig version)
if ! printf '%s\n%s\n' "$MIN_ZIG" "$ver" \
| sort -V | head -n1 | grep -qx "$MIN_ZIG"; then
fail "Zig $MIN_ZIG+ required (found $ver). This project tracks 0.16 idioms."
fi
ok "Zig $ver"
}
check_opensc() {
if ! command -v pkcs11-tool &>/dev/null; then
fail "OpenSC (pkcs11-tool) not found. Install: sudo apt install opensc"
fi
ok "OpenSC pkcs11-tool$(dpkg -l opensc 2>/dev/null | awk '/^ii/{print " "$3}')"
if [ -e /usr/lib/x86_64-linux-gnu/pkcs11-spy.so ]; then
ok "pkcs11-spy.so present (call tracing via 'just spy')"
else
warn "pkcs11-spy.so not found (optional; ships with opensc-pkcs11)"
fi
}
check_openssl() {
if command -v openssl &>/dev/null; then
ok "$(openssl version | cut -d' ' -f1-2) (libcrypto for RSA, M5)"
else
warn "OpenSSL not found (needed later for the RSA milestone). Install: sudo apt install libssl-dev"
fi
}
check_just() {
if command -v just &>/dev/null; then
ok "just $(just --version 2>/dev/null | cut -d' ' -f2)"
else
info "just not found (optional). Install: curl -sSf https://just.systems/install.sh | bash -s -- --to ~/.local/bin"
fi
}
build_module() {
info "Building the Cryptoki module (ReleaseSafe)..."
zig build
local so
so=$(find zig-out/lib -name 'libhsm.so.*' -type f | head -1)
[ -n "$so" ] || fail "Build produced no libhsm.so"
ok "Built $so ($(du -h "$so" | cut -f1))"
}
run_tests() {
info "Running ABI cross-check and unit tests..."
if zig build test >/dev/null 2>&1; then
ok "All tests passed (ck.zig matches OASIS v2.40 headers)"
else
fail "Tests failed. Run 'zig build test --summary all' for details."
fi
info "Running the dlopen smoke test..."
if zig build smoke 2>&1 | grep -q "smoke: OK"; then
ok "Smoke test passed (module loads and drives the ABI)"
else
fail "Smoke test failed. Run 'zig build smoke' for details."
fi
}
verify_load() {
info "Verifying the module loads under OpenSC..."
if pkcs11-tool --module zig-out/lib/libhsm.so -L >/dev/null 2>&1; then
ok "pkcs11-tool loaded the module and enumerated the slot"
else
fail "pkcs11-tool could not load the module."
fi
}
main() {
banner
info "Checking dependencies..."
check_zig
check_opensc
check_openssl
check_just
printf "\n"
build_module
run_tests
verify_load
printf "\n"
ok "Setup complete"
printf "\n"
printf " ${DIM}List the slot:${NC} pkcs11-tool --module zig-out/lib/libhsm.so -L\n"
printf " ${DIM}List mechanisms:${NC} pkcs11-tool --module zig-out/lib/libhsm.so -M\n"
printf " ${DIM}Trace every call:${NC} just spy -L\n"
printf " ${DIM}All commands:${NC} just\n"
printf "\n"
}
main "$@"

View File

@ -0,0 +1,122 @@
# =============================================================================
# ©AngelaMos | 2026
# justfile
# =============================================================================
set dotenv-load
set export
set shell := ["bash", "-uc"]
project := file_name(justfile_directory())
version := `git describe --tags --always 2>/dev/null || echo "dev"`
module := justfile_directory() / "zig-out/lib/libhsm.so"
spy := "/usr/lib/x86_64-linux-gnu/pkcs11-spy.so"
image := "angelamos-hsm:latest"
# =============================================================================
# Default
# =============================================================================
default:
@just --list --unsorted
# =============================================================================
# Build
# =============================================================================
[group('build')]
build:
zig build
[group('build')]
debug:
zig build -Doptimize=Debug
[group('build')]
release:
zig build -Doptimize=ReleaseSafe
# =============================================================================
# Test
# =============================================================================
[group('test')]
test:
zig build test
[group('test')]
test-verbose:
zig build test --summary all
[group('test')]
smoke:
zig build smoke
# =============================================================================
# Lint and Format
# =============================================================================
[group('lint')]
fmt:
zig fmt build.zig build.zig.zon src tests examples
[group('lint')]
fmt-check:
zig fmt --check build.zig build.zig.zon src tests examples
# =============================================================================
# PKCS#11 (drive the built module with OpenSC pkcs11-tool)
# =============================================================================
[group('pkcs11')]
inspect: build
pkcs11-tool --module {{module}} -I
[group('pkcs11')]
slots: build
pkcs11-tool --module {{module}} -L
[group('pkcs11')]
mechs: build
pkcs11-tool --module {{module}} -M
[group('pkcs11')]
spy *ARGS: build
PKCS11SPY={{module}} pkcs11-tool --module {{spy}} {{ARGS}}
# =============================================================================
# Docker (build the image, then run the end-to-end pkcs11-tool demo)
# =============================================================================
[group('docker')]
docker-build:
docker build -t {{image}} .
[group('docker')]
docker-demo: docker-build
docker run --rm {{image}}
# =============================================================================
# CI / Quality
# =============================================================================
[group('ci')]
ci: fmt-check test smoke
# =============================================================================
# Utilities
# =============================================================================
[group('util')]
info:
@echo "Project: {{project}}"
@echo "Version: {{version}}"
@echo "Module: {{module}}"
@echo "OS: {{os()}} ({{arch()}})"
@zig version | xargs -I{} echo "Zig: {}"
[group('util')]
clean:
-rm -rf zig-out
-rm -rf .zig-cache
@echo "Build artifacts cleaned"

View File

@ -0,0 +1,8 @@
/* ©AngelaMos | 2026 */
/* pkcs11.map */
PKCS11_2_40 {
global:
C_GetFunctionList;
local:
*;
};

View File

@ -0,0 +1,943 @@
// ©AngelaMos | 2026
// crypto_ops.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const state = @import("../core/state.zig");
const session = @import("../core/session.zig");
const object_store = @import("../core/object_store.zig");
const digest = @import("../crypto/digest.zig");
const mac = @import("../crypto/mac.zig");
const cipher = @import("../crypto/cipher.zig");
const ecdsa = @import("../crypto/ecdsa.zig");
const rsa = @import("../crypto/rsa.zig");
fn part(p: [*]ck.CK_BYTE, len: ck.CK_ULONG) []const u8 {
return p[0..@intCast(len)];
}
fn ctEql(a: []const u8, b: []const u8) bool {
if (a.len != b.len) return false;
var diff: u8 = 0;
for (a, b) |x, y| diff |= x ^ y;
return diff == 0;
}
fn objectClass(obj: *const object_store.Object) ?ck.CK_OBJECT_CLASS {
const v = obj.get(ck.CKA_CLASS) orelse return null;
if (v.len != @sizeOf(ck.CK_OBJECT_CLASS)) return null;
return std.mem.bytesToValue(ck.CK_OBJECT_CLASS, v[0..@sizeOf(ck.CK_OBJECT_CLASS)]);
}
fn keyType(obj: *const object_store.Object) ?ck.CK_KEY_TYPE {
const v = obj.get(ck.CKA_KEY_TYPE) orelse return null;
if (v.len != @sizeOf(ck.CK_KEY_TYPE)) return null;
return std.mem.bytesToValue(ck.CK_KEY_TYPE, v[0..@sizeOf(ck.CK_KEY_TYPE)]);
}
const KeyVal = union(enum) {
ok: []const u8,
err: ck.CK_RV,
};
fn secretKeyValue(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE, usage: ck.CK_ATTRIBUTE_TYPE) KeyVal {
const obj = inst.objects.getPtr(hKey) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (!object_store.visible(obj, inst.logged_in)) return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (objectClass(obj) != ck.CKO_SECRET_KEY) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
if (obj.has(usage) and !obj.getBool(usage)) return .{ .err = ck.CKR_KEY_FUNCTION_NOT_PERMITTED };
const a = obj.findPtr(ck.CKA_VALUE) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (a.sealed) return .{ .err = ck.CKR_USER_NOT_LOGGED_IN };
return .{ .ok = a.value };
}
const EcKey = union(enum) {
ok: struct { curve: ecdsa.Curve, material: []const u8 },
err: ck.CK_RV,
};
fn ecPrivateKey(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE) EcKey {
const obj = inst.objects.getPtr(hKey) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (!object_store.visible(obj, inst.logged_in)) return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (objectClass(obj) != ck.CKO_PRIVATE_KEY) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
if (keyType(obj) != ck.CKK_EC) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
if (obj.has(ck.CKA_SIGN) and !obj.getBool(ck.CKA_SIGN)) return .{ .err = ck.CKR_KEY_FUNCTION_NOT_PERMITTED };
const params = obj.get(ck.CKA_EC_PARAMS) orelse return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
const curve = ecdsa.curveFromParams(params) orelse return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
const sa = obj.findPtr(ck.CKA_VALUE) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (sa.sealed) return .{ .err = ck.CKR_USER_NOT_LOGGED_IN };
if (sa.value.len != curve.scalarLen()) return .{ .err = ck.CKR_FUNCTION_FAILED };
return .{ .ok = .{ .curve = curve, .material = sa.value } };
}
fn ecPublicKey(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE) EcKey {
const obj = inst.objects.getPtr(hKey) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (!object_store.visible(obj, inst.logged_in)) return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (objectClass(obj) != ck.CKO_PUBLIC_KEY) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
if (keyType(obj) != ck.CKK_EC) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
if (obj.has(ck.CKA_VERIFY) and !obj.getBool(ck.CKA_VERIFY)) return .{ .err = ck.CKR_KEY_FUNCTION_NOT_PERMITTED };
const params = obj.get(ck.CKA_EC_PARAMS) orelse return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
const curve = ecdsa.curveFromParams(params) orelse return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
const der = obj.get(ck.CKA_EC_POINT) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
const point = ecdsa.unwrapEcPoint(der) orelse return .{ .err = ck.CKR_FUNCTION_FAILED };
if (point.len != curve.pointLen()) return .{ .err = ck.CKR_FUNCTION_FAILED };
return .{ .ok = .{ .curve = curve, .material = point } };
}
const RsaPriv = union(enum) {
ok: rsa.PrivateComponents,
err: ck.CK_RV,
};
const RsaPub = union(enum) {
ok: rsa.PublicComponents,
err: ck.CK_RV,
};
fn rsaPrivateComponents(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE, usage: ck.CK_ATTRIBUTE_TYPE) RsaPriv {
const obj = inst.objects.getPtr(hKey) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (!object_store.visible(obj, inst.logged_in)) return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (objectClass(obj) != ck.CKO_PRIVATE_KEY) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
if (keyType(obj) != ck.CKK_RSA) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
if (obj.has(usage) and !obj.getBool(usage)) return .{ .err = ck.CKR_KEY_FUNCTION_NOT_PERMITTED };
if (obj.findPtr(ck.CKA_PRIVATE_EXPONENT)) |da| {
if (da.sealed) return .{ .err = ck.CKR_USER_NOT_LOGGED_IN };
}
return .{ .ok = .{
.n = obj.get(ck.CKA_MODULUS) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
.e = obj.get(ck.CKA_PUBLIC_EXPONENT) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
.d = obj.get(ck.CKA_PRIVATE_EXPONENT) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
.p = obj.get(ck.CKA_PRIME_1) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
.q = obj.get(ck.CKA_PRIME_2) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
.dmp1 = obj.get(ck.CKA_EXPONENT_1) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
.dmq1 = obj.get(ck.CKA_EXPONENT_2) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
.iqmp = obj.get(ck.CKA_COEFFICIENT) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
} };
}
fn rsaPublicComponents(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE, usage: ck.CK_ATTRIBUTE_TYPE) RsaPub {
const obj = inst.objects.getPtr(hKey) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (!object_store.visible(obj, inst.logged_in)) return .{ .err = ck.CKR_KEY_HANDLE_INVALID };
if (objectClass(obj) != ck.CKO_PUBLIC_KEY) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
if (keyType(obj) != ck.CKK_RSA) return .{ .err = ck.CKR_KEY_TYPE_INCONSISTENT };
if (obj.has(usage) and !obj.getBool(usage)) return .{ .err = ck.CKR_KEY_FUNCTION_NOT_PERMITTED };
return .{ .ok = .{
.n = obj.get(ck.CKA_MODULUS) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
.e = obj.get(ck.CKA_PUBLIC_EXPONENT) orelse return .{ .err = ck.CKR_KEY_HANDLE_INVALID },
} };
}
fn isRsaSignMech(mech: ck.CK_MECHANISM_TYPE) bool {
return switch (mech) {
ck.CKM_RSA_PKCS, ck.CKM_SHA256_RSA_PKCS, ck.CKM_RSA_PKCS_PSS, ck.CKM_SHA256_RSA_PKCS_PSS => true,
else => false,
};
}
fn mgfHash(mgf: ck.CK_RSA_PKCS_MGF_TYPE) ?rsa.Hash {
return switch (mgf) {
ck.CKG_MGF1_SHA256 => .sha256,
ck.CKG_MGF1_SHA384 => .sha384,
ck.CKG_MGF1_SHA512 => .sha512,
else => null,
};
}
const SignParamsResult = union(enum) {
ok: rsa.SignParams,
err: ck.CK_RV,
};
fn parsePss(pMechanism: *ck.CK_MECHANISM, digest_hash: rsa.Hash) SignParamsResult {
var params: rsa.SignParams = .{
.scheme = .pss,
.digest = digest_hash,
.pss_hash = if (digest_hash == .none) .sha256 else digest_hash,
};
const p = pMechanism.pParameter orelse {
if (digest_hash == .none) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
return .{ .ok = params };
};
if (pMechanism.ulParameterLen != @sizeOf(ck.CK_RSA_PKCS_PSS_PARAMS)) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
const pp: *const ck.CK_RSA_PKCS_PSS_PARAMS = @ptrCast(@alignCast(p));
const h = rsa.Hash.fromMech(pp.hashAlg) orelse return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (digest_hash != .none and h != digest_hash) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (mgfHash(pp.mgf) != h) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (pp.sLen > rsa.max_modulus_bytes) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
params.pss_hash = h;
params.salt_len = @intCast(pp.sLen);
return .{ .ok = params };
}
fn rsaSignParams(pMechanism: *ck.CK_MECHANISM) SignParamsResult {
return switch (pMechanism.mechanism) {
ck.CKM_RSA_PKCS => .{ .ok = .{ .scheme = .pkcs1, .digest = .none } },
ck.CKM_SHA256_RSA_PKCS => .{ .ok = .{ .scheme = .pkcs1, .digest = .sha256 } },
ck.CKM_RSA_PKCS_PSS => parsePss(pMechanism, .none),
ck.CKM_SHA256_RSA_PKCS_PSS => parsePss(pMechanism, .sha256),
else => .{ .err = ck.CKR_MECHANISM_INVALID },
};
}
const CryptParamsResult = union(enum) {
ok: rsa.CryptParams,
err: ck.CK_RV,
};
fn rsaCryptParams(pMechanism: *ck.CK_MECHANISM) CryptParamsResult {
return switch (pMechanism.mechanism) {
ck.CKM_RSA_PKCS => .{ .ok = .{ .scheme = .pkcs1 } },
ck.CKM_RSA_PKCS_OAEP => blk: {
const p = pMechanism.pParameter orelse break :blk .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (pMechanism.ulParameterLen != @sizeOf(ck.CK_RSA_PKCS_OAEP_PARAMS)) break :blk .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
const op: *const ck.CK_RSA_PKCS_OAEP_PARAMS = @ptrCast(@alignCast(p));
const h = rsa.Hash.fromMech(op.hashAlg) orelse break :blk .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (mgfHash(op.mgf) != h) break :blk .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (op.ulSourceDataLen != 0) break :blk .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
break :blk .{ .ok = .{ .scheme = .oaep, .oaep_hash = h } };
},
else => .{ .err = ck.CKR_MECHANISM_INVALID },
};
}
fn signLen(op: *const session.SignOp) ck.CK_ULONG {
return switch (op.*) {
.mac => |*m| @intCast(m.macLen()),
.ec => |*e| @intCast(e.sigLen()),
.rsa => |*r| @intCast(r.sig_len),
};
}
fn emitSign(inst: *state.Instance, sess: *session.Session, pSignature: ?[*]ck.CK_BYTE, pulSignatureLen: *ck.CK_ULONG) ck.CK_RV {
const op = &sess.sign_op.?;
const slen = signLen(op);
if (pSignature == null) {
pulSignatureLen.* = slen;
return ck.CKR_OK;
}
if (pulSignatureLen.* < slen) {
pulSignatureLen.* = slen;
return ck.CKR_BUFFER_TOO_SMALL;
}
const out = pSignature.?[0..@intCast(slen)];
switch (op.*) {
.mac => |*m| m.finalInto(out),
.ec => |*e| _ = e.finalInto(inst.io(), out) catch {
sess.endSign();
return ck.CKR_FUNCTION_FAILED;
},
.rsa => return ck.CKR_FUNCTION_FAILED,
}
pulSignatureLen.* = slen;
sess.endSign();
return ck.CKR_OK;
}
fn finalizeVerify(sess: *session.Session, pSignature: [*]ck.CK_BYTE, ulSignatureLen: ck.CK_ULONG) ck.CK_RV {
const sig = pSignature[0..@intCast(ulSignatureLen)];
const rv = switch (sess.verify_op.?) {
.mac => |*m| blk: {
const mlen: ck.CK_ULONG = @intCast(m.macLen());
var computed: [mac.max_mac_len]u8 = undefined;
m.finalInto(computed[0..@intCast(mlen)]);
if (ulSignatureLen != mlen) break :blk ck.CKR_SIGNATURE_LEN_RANGE;
if (!ctEql(computed[0..@intCast(mlen)], sig)) break :blk ck.CKR_SIGNATURE_INVALID;
break :blk ck.CKR_OK;
},
.ec => |*e| switch (e.finalVerify(sig)) {
.ok => ck.CKR_OK,
.invalid => ck.CKR_SIGNATURE_INVALID,
.len_range => ck.CKR_SIGNATURE_LEN_RANGE,
},
.rsa => ck.CKR_FUNCTION_FAILED,
};
sess.endVerify();
return rv;
}
fn signInitOp(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE, pMechanism: *ck.CK_MECHANISM) union(enum) { ok: session.SignOp, err: ck.CK_RV } {
const mech = pMechanism.mechanism;
if (mac.macLenOf(mech) != null) {
const val = switch (secretKeyValue(inst, hKey, ck.CKA_SIGN)) {
.err => |rv| return .{ .err = rv },
.ok => |v| v,
};
return .{ .ok = .{ .mac = mac.Mac.init(mech, val) orelse return .{ .err = ck.CKR_MECHANISM_INVALID } } };
}
if (ecdsa.hashModeOf(mech) != null) {
const k = switch (ecPrivateKey(inst, hKey)) {
.err => |rv| return .{ .err = rv },
.ok => |v| v,
};
return .{ .ok = .{ .ec = ecdsa.SignState.init(k.curve, mech, k.material) orelse return .{ .err = ck.CKR_MECHANISM_INVALID } } };
}
if (isRsaSignMech(mech)) {
const params = switch (rsaSignParams(pMechanism)) {
.err => |rv| return .{ .err = rv },
.ok => |p| p,
};
const pc = switch (rsaPrivateComponents(inst, hKey, ck.CKA_SIGN)) {
.err => |rv| return .{ .err = rv },
.ok => |c| c,
};
return .{ .ok = .{ .rsa = .{ .key = hKey, .params = params, .sig_len = pc.n.len } } };
}
return .{ .err = ck.CKR_MECHANISM_INVALID };
}
fn verifyInitOp(inst: *state.Instance, hKey: ck.CK_OBJECT_HANDLE, pMechanism: *ck.CK_MECHANISM) union(enum) { ok: session.VerifyOp, err: ck.CK_RV } {
const mech = pMechanism.mechanism;
if (mac.macLenOf(mech) != null) {
const val = switch (secretKeyValue(inst, hKey, ck.CKA_VERIFY)) {
.err => |rv| return .{ .err = rv },
.ok => |v| v,
};
return .{ .ok = .{ .mac = mac.Mac.init(mech, val) orelse return .{ .err = ck.CKR_MECHANISM_INVALID } } };
}
if (ecdsa.hashModeOf(mech) != null) {
const k = switch (ecPublicKey(inst, hKey)) {
.err => |rv| return .{ .err = rv },
.ok => |v| v,
};
return .{ .ok = .{ .ec = ecdsa.VerifyState.init(k.curve, mech, k.material) orelse return .{ .err = ck.CKR_MECHANISM_INVALID } } };
}
if (isRsaSignMech(mech)) {
const params = switch (rsaSignParams(pMechanism)) {
.err => |rv| return .{ .err = rv },
.ok => |p| p,
};
const pc = switch (rsaPublicComponents(inst, hKey, ck.CKA_VERIFY)) {
.err => |rv| return .{ .err = rv },
.ok => |c| c,
};
return .{ .ok = .{ .rsa = .{ .key = hKey, .params = params, .sig_len = pc.n.len } } };
}
return .{ .err = ck.CKR_MECHANISM_INVALID };
}
fn mapCipherErr(e: cipher.Error) ck.CK_RV {
return switch (e) {
cipher.Error.DataLenRange => ck.CKR_DATA_LEN_RANGE,
cipher.Error.EncryptedDataLenRange => ck.CKR_ENCRYPTED_DATA_LEN_RANGE,
cipher.Error.EncryptedDataInvalid => ck.CKR_ENCRYPTED_DATA_INVALID,
cipher.Error.KeySize => ck.CKR_KEY_SIZE_RANGE,
cipher.Error.AadTooLarge => ck.CKR_ARGUMENTS_BAD,
cipher.Error.IvInvalid => ck.CKR_MECHANISM_PARAM_INVALID,
};
}
const CipherInit = union(enum) {
ok: cipher.Cipher,
err: ck.CK_RV,
};
fn buildCipher(inst: *state.Instance, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE, encrypt: bool, usage: ck.CK_ATTRIBUTE_TYPE) CipherInit {
const mode = cipher.modeOf(pMechanism.mechanism) orelse return .{ .err = ck.CKR_MECHANISM_INVALID };
const val = switch (secretKeyValue(inst, hKey, usage)) {
.err => |rv| return .{ .err = rv },
.ok => |v| v,
};
if (!cipher.validKeyLen(val.len)) return .{ .err = ck.CKR_KEY_SIZE_RANGE };
var c: cipher.Cipher = .{ .mode = mode, .encrypt = encrypt, .key_len = @intCast(val.len) };
@memcpy(c.key_buf[0..val.len], val);
switch (mode) {
.cbc, .cbc_pad => {
const p = pMechanism.pParameter orelse return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (pMechanism.ulParameterLen != config.aes_block_len) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
@memcpy(&c.chain, @as([*]const u8, @ptrCast(p))[0..config.aes_block_len]);
},
.gcm => {
const p = pMechanism.pParameter orelse return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (pMechanism.ulParameterLen != @sizeOf(ck.CK_GCM_PARAMS)) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
const gp: *const ck.CK_GCM_PARAMS = @ptrCast(@alignCast(p));
if (gp.ulIvLen != config.gcm_iv_len) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (gp.ulIvBits != 0 and gp.ulIvBits != config.gcm_iv_bits) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
if (gp.ulTagBits != config.gcm_tag_bits) return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
const ivp = gp.pIv orelse return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
@memcpy(&c.iv, ivp[0..config.gcm_iv_len]);
const aad_len: usize = @intCast(gp.ulAADLen);
if (aad_len > config.max_gcm_aad_len) return .{ .err = ck.CKR_ARGUMENTS_BAD };
if (aad_len > 0) {
const ap = gp.pAAD orelse return .{ .err = ck.CKR_MECHANISM_PARAM_INVALID };
@memcpy(c.aad_buf[0..aad_len], ap[0..aad_len]);
}
c.aad_len = aad_len;
},
}
return .{ .ok = c };
}
fn updateOutLen(op: *const cipher.Cipher, in_len: usize) ck.CK_ULONG {
return @intCast(((op.partial_len + in_len) / config.aes_block_len) * config.aes_block_len);
}
fn emitDigest(sess: *session.Session, pDigest: ?[*]ck.CK_BYTE, pulDigestLen: *ck.CK_ULONG) ck.CK_RV {
const op = &sess.digest_op.?;
const dlen: ck.CK_ULONG = @intCast(op.digestLen());
if (pDigest == null) {
pulDigestLen.* = dlen;
return ck.CKR_OK;
}
if (pulDigestLen.* < dlen) {
pulDigestLen.* = dlen;
return ck.CKR_BUFFER_TOO_SMALL;
}
op.finalInto(pDigest.?[0..@intCast(dlen)]);
pulDigestLen.* = dlen;
sess.endDigest();
return ck.CKR_OK;
}
fn isRsaCryptMech(mech: ck.CK_MECHANISM_TYPE) bool {
return mech == ck.CKM_RSA_PKCS or mech == ck.CKM_RSA_PKCS_OAEP;
}
const RsaCryptResult = union(enum) {
ok: session.RsaCrypt,
err: ck.CK_RV,
};
fn rsaCryptInit(inst: *state.Instance, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE, private: bool, usage: ck.CK_ATTRIBUTE_TYPE) RsaCryptResult {
const params = switch (rsaCryptParams(pMechanism)) {
.err => |rv| return .{ .err = rv },
.ok => |p| p,
};
const mod_len = if (private) switch (rsaPrivateComponents(inst, hKey, usage)) {
.err => |rv| return .{ .err = rv },
.ok => |c| c.n.len,
} else switch (rsaPublicComponents(inst, hKey, usage)) {
.err => |rv| return .{ .err = rv },
.ok => |c| c.n.len,
};
return .{ .ok = .{ .key = hKey, .params = params, .out_len = mod_len } };
}
pub fn C_EncryptInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.encrypt_op != null) return ck.CKR_OPERATION_ACTIVE;
if (cipher.modeOf(pMechanism.mechanism) != null) {
sess.encrypt_op = .{ .aes = switch (buildCipher(inst, pMechanism, hKey, true, ck.CKA_ENCRYPT)) {
.err => |rv| return rv,
.ok => |c| c,
} };
return ck.CKR_OK;
}
if (isRsaCryptMech(pMechanism.mechanism)) {
sess.encrypt_op = .{ .rsa = switch (rsaCryptInit(inst, pMechanism, hKey, false, ck.CKA_ENCRYPT)) {
.err => |rv| return rv,
.ok => |o| o,
} };
return ck.CKR_OK;
}
return ck.CKR_MECHANISM_INVALID;
}
pub fn C_Encrypt(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: ck.CK_ULONG, pEncryptedData: ?[*]ck.CK_BYTE, pulEncryptedDataLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.encrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const in = part(pData, ulDataLen);
switch (op.*) {
.aes => |*c| {
const need: ck.CK_ULONG = @intCast(cipher.encryptOutLen(c.mode, in.len));
if (pEncryptedData == null) {
pulEncryptedDataLen.* = need;
return ck.CKR_OK;
}
if (pulEncryptedDataLen.* < need) {
pulEncryptedDataLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const out = pEncryptedData.?[0..@intCast(need)];
var n: usize = 0;
switch (c.mode) {
.gcm => n = c.gcmEncrypt(in, out),
.cbc, .cbc_pad => {
n = c.encryptUpdate(in, out);
n += c.encryptFinal(out[n..]) catch |e| {
sess.endEncrypt();
return mapCipherErr(e);
};
},
}
pulEncryptedDataLen.* = @intCast(n);
sess.endEncrypt();
return ck.CKR_OK;
},
.rsa => |*r| {
const need: ck.CK_ULONG = @intCast(r.out_len);
if (pEncryptedData == null) {
pulEncryptedDataLen.* = need;
return ck.CKR_OK;
}
if (pulEncryptedDataLen.* < need) {
pulEncryptedDataLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const pc = switch (rsaPublicComponents(inst, r.key, ck.CKA_ENCRYPT)) {
.err => |rv| {
sess.endEncrypt();
return rv;
},
.ok => |c| c,
};
const n = rsa.encrypt(pc, r.params, in, pEncryptedData.?[0..@intCast(need)]) catch {
sess.endEncrypt();
return ck.CKR_DATA_LEN_RANGE;
};
pulEncryptedDataLen.* = @intCast(n);
sess.endEncrypt();
return ck.CKR_OK;
},
}
}
pub fn C_EncryptUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG, pEncryptedPart: ?[*]ck.CK_BYTE, pulEncryptedPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.encrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const c = switch (op.*) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
.aes => |*x| x,
};
if (c.mode == .gcm) return ck.CKR_FUNCTION_NOT_SUPPORTED;
const in = part(pPart, ulPartLen);
const need = updateOutLen(c, in.len);
if (pEncryptedPart == null) {
pulEncryptedPartLen.* = need;
return ck.CKR_OK;
}
if (pulEncryptedPartLen.* < need) {
pulEncryptedPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
pulEncryptedPartLen.* = @intCast(c.encryptUpdate(in, pEncryptedPart.?[0..@intCast(need)]));
return ck.CKR_OK;
}
pub fn C_EncryptFinal(hSession: ck.CK_SESSION_HANDLE, pLastEncryptedPart: ?[*]ck.CK_BYTE, pulLastEncryptedPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.encrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const c = switch (op.*) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
.aes => |*x| x,
};
if (c.mode == .gcm) return ck.CKR_FUNCTION_NOT_SUPPORTED;
const need: ck.CK_ULONG = if (c.mode == .cbc_pad) config.aes_block_len else 0;
if (pLastEncryptedPart == null) {
pulLastEncryptedPartLen.* = need;
return ck.CKR_OK;
}
if (pulLastEncryptedPartLen.* < need) {
pulLastEncryptedPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = c.encryptFinal(pLastEncryptedPart.?[0..@intCast(need)]) catch |e| {
sess.endEncrypt();
return mapCipherErr(e);
};
pulLastEncryptedPartLen.* = @intCast(n);
sess.endEncrypt();
return ck.CKR_OK;
}
pub fn C_DecryptInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.decrypt_op != null) return ck.CKR_OPERATION_ACTIVE;
if (cipher.modeOf(pMechanism.mechanism) != null) {
sess.decrypt_op = .{ .aes = switch (buildCipher(inst, pMechanism, hKey, false, ck.CKA_DECRYPT)) {
.err => |rv| return rv,
.ok => |c| c,
} };
return ck.CKR_OK;
}
if (isRsaCryptMech(pMechanism.mechanism)) {
sess.decrypt_op = .{ .rsa = switch (rsaCryptInit(inst, pMechanism, hKey, true, ck.CKA_DECRYPT)) {
.err => |rv| return rv,
.ok => |o| o,
} };
return ck.CKR_OK;
}
return ck.CKR_MECHANISM_INVALID;
}
pub fn C_Decrypt(hSession: ck.CK_SESSION_HANDLE, pEncryptedData: [*]ck.CK_BYTE, ulEncryptedDataLen: ck.CK_ULONG, pData: ?[*]ck.CK_BYTE, pulDataLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.decrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const in = part(pEncryptedData, ulEncryptedDataLen);
switch (op.*) {
.aes => |*c| {
const need: ck.CK_ULONG = @intCast(cipher.decryptOutLen(c.mode, in.len));
if (pData == null) {
pulDataLen.* = need;
return ck.CKR_OK;
}
if (pulDataLen.* < need) {
pulDataLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const out = pData.?[0..@intCast(need)];
var n: usize = 0;
switch (c.mode) {
.gcm => n = c.gcmDecrypt(in, out) catch |e| {
sess.endDecrypt();
return mapCipherErr(e);
},
.cbc, .cbc_pad => {
n = c.decryptUpdate(in, out);
n += c.decryptFinal(out[n..]) catch |e| {
sess.endDecrypt();
return mapCipherErr(e);
};
},
}
pulDataLen.* = @intCast(n);
sess.endDecrypt();
return ck.CKR_OK;
},
.rsa => |*r| {
const need: ck.CK_ULONG = @intCast(r.out_len);
if (pData == null) {
pulDataLen.* = need;
return ck.CKR_OK;
}
if (pulDataLen.* < need) {
pulDataLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const sc = switch (rsaPrivateComponents(inst, r.key, ck.CKA_DECRYPT)) {
.err => |rv| {
sess.endDecrypt();
return rv;
},
.ok => |c| c,
};
const n = rsa.decrypt(sc, r.params, in, pData.?[0..@intCast(need)]) catch {
sess.endDecrypt();
return ck.CKR_ENCRYPTED_DATA_INVALID;
};
pulDataLen.* = @intCast(n);
sess.endDecrypt();
return ck.CKR_OK;
},
}
}
pub fn C_DecryptUpdate(hSession: ck.CK_SESSION_HANDLE, pEncryptedPart: [*]ck.CK_BYTE, ulEncryptedPartLen: ck.CK_ULONG, pPart: ?[*]ck.CK_BYTE, pulPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.decrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const c = switch (op.*) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
.aes => |*x| x,
};
if (c.mode == .gcm) return ck.CKR_FUNCTION_NOT_SUPPORTED;
const in = part(pEncryptedPart, ulEncryptedPartLen);
const need = updateOutLen(c, in.len);
if (pPart == null) {
pulPartLen.* = need;
return ck.CKR_OK;
}
if (pulPartLen.* < need) {
pulPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
pulPartLen.* = @intCast(c.decryptUpdate(in, pPart.?[0..@intCast(need)]));
return ck.CKR_OK;
}
pub fn C_DecryptFinal(hSession: ck.CK_SESSION_HANDLE, pLastPart: ?[*]ck.CK_BYTE, pulLastPartLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const op = if (sess.decrypt_op) |*o| o else return ck.CKR_OPERATION_NOT_INITIALIZED;
const c = switch (op.*) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
.aes => |*x| x,
};
if (c.mode == .gcm) return ck.CKR_FUNCTION_NOT_SUPPORTED;
const need: ck.CK_ULONG = if (c.mode == .cbc_pad) config.aes_block_len else 0;
if (pLastPart == null) {
pulLastPartLen.* = need;
return ck.CKR_OK;
}
if (pulLastPartLen.* < need) {
pulLastPartLen.* = need;
return ck.CKR_BUFFER_TOO_SMALL;
}
const n = c.decryptFinal(pLastPart.?[0..@intCast(need)]) catch |e| {
sess.endDecrypt();
return mapCipherErr(e);
};
pulLastPartLen.* = @intCast(n);
sess.endDecrypt();
return ck.CKR_OK;
}
pub fn C_DigestInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op != null) return ck.CKR_OPERATION_ACTIVE;
sess.digest_op = digest.Hasher.init(pMechanism.mechanism) orelse return ck.CKR_MECHANISM_INVALID;
return ck.CKR_OK;
}
pub fn C_Digest(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: ck.CK_ULONG, pDigest: ?[*]ck.CK_BYTE, pulDigestLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
const dlen: ck.CK_ULONG = @intCast(sess.digest_op.?.digestLen());
if (pDigest == null) {
pulDigestLen.* = dlen;
return ck.CKR_OK;
}
if (pulDigestLen.* < dlen) {
pulDigestLen.* = dlen;
return ck.CKR_BUFFER_TOO_SMALL;
}
sess.digest_op.?.update(part(pData, ulDataLen));
return emitDigest(sess, pDigest, pulDigestLen);
}
pub fn C_DigestUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
sess.digest_op.?.update(part(pPart, ulPartLen));
return ck.CKR_OK;
}
pub fn C_DigestKey(hSession: ck.CK_SESSION_HANDLE, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
const obj = inst.objects.getPtr(hKey) orelse return ck.CKR_KEY_HANDLE_INVALID;
if (!object_store.visible(obj, inst.logged_in)) return ck.CKR_KEY_HANDLE_INVALID;
const val = obj.get(ck.CKA_VALUE) orelse return ck.CKR_KEY_HANDLE_INVALID;
sess.digest_op.?.update(val);
return ck.CKR_OK;
}
pub fn C_DigestFinal(hSession: ck.CK_SESSION_HANDLE, pDigest: ?[*]ck.CK_BYTE, pulDigestLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.digest_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
return emitDigest(sess, pDigest, pulDigestLen);
}
pub fn C_SignInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op != null) return ck.CKR_OPERATION_ACTIVE;
sess.sign_op = switch (signInitOp(inst, hKey, pMechanism)) {
.err => |rv| return rv,
.ok => |op| op,
};
return ck.CKR_OK;
}
pub fn C_Sign(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: ck.CK_ULONG, pSignature: ?[*]ck.CK_BYTE, pulSignatureLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
const slen = signLen(&sess.sign_op.?);
if (pSignature == null) {
pulSignatureLen.* = slen;
return ck.CKR_OK;
}
if (pulSignatureLen.* < slen) {
pulSignatureLen.* = slen;
return ck.CKR_BUFFER_TOO_SMALL;
}
switch (sess.sign_op.?) {
.rsa => |op| {
const out = pSignature.?[0..@intCast(slen)];
const sc = switch (rsaPrivateComponents(inst, op.key, ck.CKA_SIGN)) {
.err => |rv| {
sess.endSign();
return rv;
},
.ok => |c| c,
};
const n = rsa.sign(sc, op.params, part(pData, ulDataLen), out) catch {
sess.endSign();
return ck.CKR_FUNCTION_FAILED;
};
pulSignatureLen.* = @intCast(n);
sess.endSign();
return ck.CKR_OK;
},
else => {
sess.sign_op.?.update(part(pData, ulDataLen));
return emitSign(inst, sess, pSignature, pulSignatureLen);
},
}
}
pub fn C_SignUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
switch (sess.sign_op.?) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
else => {},
}
sess.sign_op.?.update(part(pPart, ulPartLen));
return ck.CKR_OK;
}
pub fn C_SignFinal(hSession: ck.CK_SESSION_HANDLE, pSignature: ?[*]ck.CK_BYTE, pulSignatureLen: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.sign_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
switch (sess.sign_op.?) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
else => {},
}
return emitSign(inst, sess, pSignature, pulSignatureLen);
}
pub fn C_SignRecoverInit(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_SignRecover(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_VerifyInit(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, hKey: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.verify_op != null) return ck.CKR_OPERATION_ACTIVE;
sess.verify_op = switch (verifyInitOp(inst, hKey, pMechanism)) {
.err => |rv| return rv,
.ok => |op| op,
};
return ck.CKR_OK;
}
pub fn C_Verify(hSession: ck.CK_SESSION_HANDLE, pData: [*]ck.CK_BYTE, ulDataLen: ck.CK_ULONG, pSignature: [*]ck.CK_BYTE, ulSignatureLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.verify_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
switch (sess.verify_op.?) {
.rsa => |op| {
const data = part(pData, ulDataLen);
sess.endVerify();
if (ulSignatureLen != op.sig_len) return ck.CKR_SIGNATURE_LEN_RANGE;
const pc = switch (rsaPublicComponents(inst, op.key, ck.CKA_VERIFY)) {
.err => |rv| return rv,
.ok => |c| c,
};
const r = rsa.verify(pc, op.params, data, pSignature[0..@intCast(ulSignatureLen)]) catch return ck.CKR_FUNCTION_FAILED;
return switch (r) {
.ok => ck.CKR_OK,
.invalid => ck.CKR_SIGNATURE_INVALID,
};
},
else => {
sess.verify_op.?.update(part(pData, ulDataLen));
return finalizeVerify(sess, pSignature, ulSignatureLen);
},
}
}
pub fn C_VerifyUpdate(hSession: ck.CK_SESSION_HANDLE, pPart: [*]ck.CK_BYTE, ulPartLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.verify_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
switch (sess.verify_op.?) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
else => {},
}
sess.verify_op.?.update(part(pPart, ulPartLen));
return ck.CKR_OK;
}
pub fn C_VerifyFinal(hSession: ck.CK_SESSION_HANDLE, pSignature: [*]ck.CK_BYTE, ulSignatureLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.verify_op == null) return ck.CKR_OPERATION_NOT_INITIALIZED;
switch (sess.verify_op.?) {
.rsa => return ck.CKR_FUNCTION_NOT_SUPPORTED,
else => {},
}
return finalizeVerify(sess, pSignature, ulSignatureLen);
}
pub fn C_VerifyRecoverInit(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_VerifyRecover(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_DigestEncryptUpdate(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_DecryptDigestUpdate(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_SignEncryptUpdate(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_DecryptVerifyUpdate(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}

View File

@ -0,0 +1,49 @@
// ©AngelaMos | 2026
// general.zig
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const util = @import("../util.zig");
const state = @import("../core/state.zig");
pub fn C_Initialize(pInitArgs: ?*anyopaque) callconv(.c) ck.CK_RV {
state.mutex.lock();
defer state.mutex.unlock();
if (state.isInitialized()) return ck.CKR_CRYPTOKI_ALREADY_INITIALIZED;
switch (state.parseInitArgs(pInitArgs)) {
.err => |rv| return rv,
.ok => |locking| state.initialize(locking),
}
return ck.CKR_OK;
}
pub fn C_Finalize(pReserved: ?*anyopaque) callconv(.c) ck.CK_RV {
if (pReserved != null) return ck.CKR_ARGUMENTS_BAD;
return state.finalize();
}
pub fn C_GetInfo(pInfo: *ck.CK_INFO) callconv(.c) ck.CK_RV {
state.mutex.lock();
defer state.mutex.unlock();
if (!state.isInitialized()) return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
pInfo.* = .{
.cryptokiVersion = config.cryptoki_version,
.manufacturerID = util.padded(32, config.manufacturer_id),
.flags = 0,
.libraryDescription = util.padded(32, config.library_description),
.libraryVersion = config.library_version,
};
return ck.CKR_OK;
}
pub fn C_GetFunctionStatus(_: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_PARALLEL;
}
pub fn C_CancelFunction(_: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_PARALLEL;
}
pub fn C_WaitForSlotEvent(_: ck.CK_FLAGS, _: *ck.CK_SLOT_ID, _: ?*anyopaque) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}

View File

@ -0,0 +1,230 @@
// ©AngelaMos | 2026
// keymgmt.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const state = @import("../core/state.zig");
const object_store = @import("../core/object_store.zig");
const object = @import("object.zig");
const ecdsa = @import("../crypto/ecdsa.zig");
const rsa = @import("../crypto/rsa.zig");
const Object = object_store.Object;
fn attrBytes(a: ck.CK_ATTRIBUTE) []const u8 {
const ptr = a.pValue orelse return &.{};
return @as([*]const u8, @ptrCast(ptr))[0..@intCast(a.ulValueLen)];
}
fn ulongFrom(bytes: []const u8) ?ck.CK_ULONG {
if (bytes.len != @sizeOf(ck.CK_ULONG)) return null;
return std.mem.bytesToValue(ck.CK_ULONG, bytes[0..@sizeOf(ck.CK_ULONG)]);
}
pub fn C_GenerateKey(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG, phKey: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (pMechanism.mechanism != ck.CKM_AES_KEY_GEN) return ck.CKR_MECHANISM_INVALID;
const allocator = inst.allocator();
const template = if (ulCount == 0) &[_]ck.CK_ATTRIBUTE{} else pTemplate[0..@intCast(ulCount)];
var key_len: usize = 0;
var have_len = false;
for (template) |a| {
if (a.type == ck.CKA_VALUE_LEN) {
const v = ulongFrom(attrBytes(a)) orelse return ck.CKR_ATTRIBUTE_VALUE_INVALID;
key_len = @intCast(v);
have_len = true;
}
}
if (!have_len) return ck.CKR_TEMPLATE_INCOMPLETE;
if (key_len != config.aes_min_key_bytes and key_len != config.aes_max_key_bytes) return ck.CKR_KEY_SIZE_RANGE;
var obj: Object = .{};
var moved = false;
defer if (!moved) obj.deinit(allocator);
for (template) |a| {
obj.set(allocator, a.type, attrBytes(a)) catch |e| return object_store.mapSetErr(e);
}
var key_bytes: [32]u8 = undefined;
defer std.crypto.secureZero(u8, &key_bytes);
inst.io().randomSecure(key_bytes[0..key_len]) catch return ck.CKR_FUNCTION_FAILED;
var class_val: ck.CK_OBJECT_CLASS = ck.CKO_SECRET_KEY;
var type_val: ck.CK_KEY_TYPE = ck.CKK_AES;
obj.set(allocator, ck.CKA_CLASS, std.mem.asBytes(&class_val)) catch |e| return object_store.mapSetErr(e);
obj.set(allocator, ck.CKA_KEY_TYPE, std.mem.asBytes(&type_val)) catch |e| return object_store.mapSetErr(e);
obj.set(allocator, ck.CKA_VALUE, key_bytes[0..key_len]) catch |e| return object_store.mapSetErr(e);
obj.set(allocator, ck.CKA_LOCAL, &[_]u8{ck.CK_TRUE}) catch |e| return object_store.mapSetErr(e);
const kgm: ck.CK_MECHANISM_TYPE = ck.CKM_AES_KEY_GEN;
obj.set(allocator, ck.CKA_KEY_GEN_MECHANISM, std.mem.asBytes(&kgm)) catch |e| return object_store.mapSetErr(e);
if (!obj.has(ck.CKA_SENSITIVE)) obj.set(allocator, ck.CKA_SENSITIVE, &[_]u8{ck.CK_TRUE}) catch |e| return object_store.mapSetErr(e);
if (!obj.has(ck.CKA_EXTRACTABLE)) obj.set(allocator, ck.CKA_EXTRACTABLE, &[_]u8{ck.CK_FALSE}) catch |e| return object_store.mapSetErr(e);
const always_sensitive: u8 = if (obj.getBool(ck.CKA_SENSITIVE)) ck.CK_TRUE else ck.CK_FALSE;
const never_extractable: u8 = if (!obj.getBool(ck.CKA_EXTRACTABLE)) ck.CK_TRUE else ck.CK_FALSE;
obj.set(allocator, ck.CKA_ALWAYS_SENSITIVE, &[_]u8{always_sensitive}) catch |e| return object_store.mapSetErr(e);
obj.set(allocator, ck.CKA_NEVER_EXTRACTABLE, &[_]u8{never_extractable}) catch |e| return object_store.mapSetErr(e);
object.materializeDefaults(&obj, allocator, ck.CKO_SECRET_KEY) catch |e| return object_store.mapSetErr(e);
moved = true;
return object.insertNew(inst, sess, obj, phKey);
}
fn ecParamsFrom(template: []const ck.CK_ATTRIBUTE) ?[]const u8 {
for (template) |a| {
if (a.type == ck.CKA_EC_PARAMS) return attrBytes(a);
}
return null;
}
fn modulusBitsFrom(template: []const ck.CK_ATTRIBUTE) ?ck.CK_ULONG {
for (template) |a| {
if (a.type == ck.CKA_MODULUS_BITS) return ulongFrom(attrBytes(a));
}
return null;
}
fn applySensitivityDefaults(obj: *Object, allocator: std.mem.Allocator, kgm: ck.CK_MECHANISM_TYPE) !void {
try obj.set(allocator, ck.CKA_LOCAL, &[_]u8{ck.CK_TRUE});
if (!obj.has(ck.CKA_SENSITIVE)) try obj.set(allocator, ck.CKA_SENSITIVE, &[_]u8{ck.CK_TRUE});
if (!obj.has(ck.CKA_EXTRACTABLE)) try obj.set(allocator, ck.CKA_EXTRACTABLE, &[_]u8{ck.CK_FALSE});
const always_sensitive: u8 = if (obj.getBool(ck.CKA_SENSITIVE)) ck.CK_TRUE else ck.CK_FALSE;
const never_extractable: u8 = if (!obj.getBool(ck.CKA_EXTRACTABLE)) ck.CK_TRUE else ck.CK_FALSE;
try obj.set(allocator, ck.CKA_ALWAYS_SENSITIVE, &[_]u8{always_sensitive});
try obj.set(allocator, ck.CKA_NEVER_EXTRACTABLE, &[_]u8{never_extractable});
const m: ck.CK_MECHANISM_TYPE = kgm;
try obj.set(allocator, ck.CKA_KEY_GEN_MECHANISM, std.mem.asBytes(&m));
}
fn buildEcPublic(obj: *Object, allocator: std.mem.Allocator, template: []const ck.CK_ATTRIBUTE, curve: ecdsa.Curve, point: []const u8) !void {
for (template) |a| try obj.set(allocator, a.type, attrBytes(a));
const class_val: ck.CK_OBJECT_CLASS = ck.CKO_PUBLIC_KEY;
const type_val: ck.CK_KEY_TYPE = ck.CKK_EC;
try obj.set(allocator, ck.CKA_CLASS, std.mem.asBytes(&class_val));
try obj.set(allocator, ck.CKA_KEY_TYPE, std.mem.asBytes(&type_val));
try obj.set(allocator, ck.CKA_EC_PARAMS, curve.oidDer());
var der_buf: [ecdsa.max_ec_point_der]u8 = undefined;
try obj.set(allocator, ck.CKA_EC_POINT, ecdsa.wrapEcPoint(&der_buf, point));
try obj.set(allocator, ck.CKA_LOCAL, &[_]u8{ck.CK_TRUE});
const kgm: ck.CK_MECHANISM_TYPE = ck.CKM_EC_KEY_PAIR_GEN;
try obj.set(allocator, ck.CKA_KEY_GEN_MECHANISM, std.mem.asBytes(&kgm));
try object.materializeDefaults(obj, allocator, ck.CKO_PUBLIC_KEY);
}
fn buildEcPrivate(obj: *Object, allocator: std.mem.Allocator, template: []const ck.CK_ATTRIBUTE, curve: ecdsa.Curve, scalar: []const u8) !void {
for (template) |a| try obj.set(allocator, a.type, attrBytes(a));
const class_val: ck.CK_OBJECT_CLASS = ck.CKO_PRIVATE_KEY;
const type_val: ck.CK_KEY_TYPE = ck.CKK_EC;
try obj.set(allocator, ck.CKA_CLASS, std.mem.asBytes(&class_val));
try obj.set(allocator, ck.CKA_KEY_TYPE, std.mem.asBytes(&type_val));
try obj.set(allocator, ck.CKA_EC_PARAMS, curve.oidDer());
try obj.set(allocator, ck.CKA_VALUE, scalar);
try applySensitivityDefaults(obj, allocator, ck.CKM_EC_KEY_PAIR_GEN);
try object.materializeDefaults(obj, allocator, ck.CKO_PRIVATE_KEY);
}
fn buildRsaPublic(obj: *Object, allocator: std.mem.Allocator, template: []const ck.CK_ATTRIBUTE, g: *const rsa.Generated) !void {
for (template) |a| try obj.set(allocator, a.type, attrBytes(a));
const class_val: ck.CK_OBJECT_CLASS = ck.CKO_PUBLIC_KEY;
const type_val: ck.CK_KEY_TYPE = ck.CKK_RSA;
const bits: ck.CK_ULONG = g.bits;
try obj.set(allocator, ck.CKA_CLASS, std.mem.asBytes(&class_val));
try obj.set(allocator, ck.CKA_KEY_TYPE, std.mem.asBytes(&type_val));
try obj.set(allocator, ck.CKA_MODULUS, g.n.slice());
try obj.set(allocator, ck.CKA_PUBLIC_EXPONENT, g.e.slice());
try obj.set(allocator, ck.CKA_MODULUS_BITS, std.mem.asBytes(&bits));
try obj.set(allocator, ck.CKA_LOCAL, &[_]u8{ck.CK_TRUE});
const kgm: ck.CK_MECHANISM_TYPE = ck.CKM_RSA_PKCS_KEY_PAIR_GEN;
try obj.set(allocator, ck.CKA_KEY_GEN_MECHANISM, std.mem.asBytes(&kgm));
try object.materializeDefaults(obj, allocator, ck.CKO_PUBLIC_KEY);
}
fn buildRsaPrivate(obj: *Object, allocator: std.mem.Allocator, template: []const ck.CK_ATTRIBUTE, g: *const rsa.Generated) !void {
for (template) |a| try obj.set(allocator, a.type, attrBytes(a));
const class_val: ck.CK_OBJECT_CLASS = ck.CKO_PRIVATE_KEY;
const type_val: ck.CK_KEY_TYPE = ck.CKK_RSA;
try obj.set(allocator, ck.CKA_CLASS, std.mem.asBytes(&class_val));
try obj.set(allocator, ck.CKA_KEY_TYPE, std.mem.asBytes(&type_val));
try obj.set(allocator, ck.CKA_MODULUS, g.n.slice());
try obj.set(allocator, ck.CKA_PUBLIC_EXPONENT, g.e.slice());
try obj.set(allocator, ck.CKA_PRIVATE_EXPONENT, g.d.slice());
try obj.set(allocator, ck.CKA_PRIME_1, g.p.slice());
try obj.set(allocator, ck.CKA_PRIME_2, g.q.slice());
try obj.set(allocator, ck.CKA_EXPONENT_1, g.dmp1.slice());
try obj.set(allocator, ck.CKA_EXPONENT_2, g.dmq1.slice());
try obj.set(allocator, ck.CKA_COEFFICIENT, g.iqmp.slice());
try applySensitivityDefaults(obj, allocator, ck.CKM_RSA_PKCS_KEY_PAIR_GEN);
try object.materializeDefaults(obj, allocator, ck.CKO_PRIVATE_KEY);
}
pub fn C_GenerateKeyPair(hSession: ck.CK_SESSION_HANDLE, pMechanism: *ck.CK_MECHANISM, pPublicKeyTemplate: [*]ck.CK_ATTRIBUTE, ulPublicKeyAttributeCount: ck.CK_ULONG, pPrivateKeyTemplate: [*]ck.CK_ATTRIBUTE, ulPrivateKeyAttributeCount: ck.CK_ULONG, phPublicKey: *ck.CK_OBJECT_HANDLE, phPrivateKey: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const allocator = inst.allocator();
const pub_template = if (ulPublicKeyAttributeCount == 0) &[_]ck.CK_ATTRIBUTE{} else pPublicKeyTemplate[0..@intCast(ulPublicKeyAttributeCount)];
const priv_template = if (ulPrivateKeyAttributeCount == 0) &[_]ck.CK_ATTRIBUTE{} else pPrivateKeyTemplate[0..@intCast(ulPrivateKeyAttributeCount)];
var pub_obj: Object = .{};
var pub_moved = false;
defer if (!pub_moved) pub_obj.deinit(allocator);
var priv_obj: Object = .{};
var priv_moved = false;
defer if (!priv_moved) priv_obj.deinit(allocator);
switch (pMechanism.mechanism) {
ck.CKM_EC_KEY_PAIR_GEN => {
const params = ecParamsFrom(pub_template) orelse return ck.CKR_TEMPLATE_INCOMPLETE;
const curve = ecdsa.curveFromParams(params) orelse return ck.CKR_DOMAIN_PARAMS_INVALID;
var km = ecdsa.generate(inst.io(), curve) catch return ck.CKR_FUNCTION_FAILED;
defer std.crypto.secureZero(u8, &km.scalar);
buildEcPublic(&pub_obj, allocator, pub_template, curve, km.pointBytes()) catch |e| return object_store.mapSetErr(e);
buildEcPrivate(&priv_obj, allocator, priv_template, curve, km.scalarBytes()) catch |e| return object_store.mapSetErr(e);
},
ck.CKM_RSA_PKCS_KEY_PAIR_GEN => {
const bits = modulusBitsFrom(pub_template) orelse return ck.CKR_TEMPLATE_INCOMPLETE;
if (bits < config.rsa_min_key_bits or bits > config.rsa_max_key_bits) return ck.CKR_KEY_SIZE_RANGE;
var g = rsa.generate(@intCast(bits)) catch return ck.CKR_FUNCTION_FAILED;
defer g.zeroize();
buildRsaPublic(&pub_obj, allocator, pub_template, &g) catch |e| return object_store.mapSetErr(e);
buildRsaPrivate(&priv_obj, allocator, priv_template, &g) catch |e| return object_store.mapSetErr(e);
},
else => return ck.CKR_MECHANISM_INVALID,
}
const pub_is_token = pub_obj.isToken();
pub_moved = true;
const pub_rv = object.insertNew(inst, sess, pub_obj, phPublicKey);
if (pub_rv != ck.CKR_OK) return pub_rv;
priv_moved = true;
const priv_rv = object.insertNew(inst, sess, priv_obj, phPrivateKey);
if (priv_rv != ck.CKR_OK) {
_ = inst.objects.destroy(allocator, phPublicKey.*);
if (pub_is_token) object_store.save(inst.io(), allocator, &inst.objects, inst.mk) catch {};
return priv_rv;
}
return ck.CKR_OK;
}
pub fn C_WrapKey(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE, _: ck.CK_OBJECT_HANDLE, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_UnwrapKey(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: [*]ck.CK_ATTRIBUTE, _: ck.CK_ULONG, _: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_DeriveKey(_: ck.CK_SESSION_HANDLE, _: *ck.CK_MECHANISM, _: ck.CK_OBJECT_HANDLE, _: ?[*]ck.CK_ATTRIBUTE, _: ck.CK_ULONG, _: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}

View File

@ -0,0 +1,337 @@
// ©AngelaMos | 2026
// object.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const state = @import("../core/state.zig");
const session = @import("../core/session.zig");
const object_store = @import("../core/object_store.zig");
const Object = object_store.Object;
const mapSetErr = object_store.mapSetErr;
fn inputTemplate(p: ?[*]ck.CK_ATTRIBUTE, count: ck.CK_ULONG) []ck.CK_ATTRIBUTE {
if (count == 0) return &.{};
return (p orelse return &.{})[0..@intCast(count)];
}
fn attrBytes(a: ck.CK_ATTRIBUTE) []const u8 {
const ptr = a.pValue orelse return &.{};
return @as([*]const u8, @ptrCast(ptr))[0..@intCast(a.ulValueLen)];
}
fn ulongAttr(obj: *const Object, t: ck.CK_ATTRIBUTE_TYPE) ?ck.CK_ULONG {
const v = obj.get(t) orelse return null;
if (v.len != @sizeOf(ck.CK_ULONG)) return null;
return std.mem.bytesToValue(ck.CK_ULONG, v[0..@sizeOf(ck.CK_ULONG)]);
}
const visible = object_store.visible;
fn matches(obj: *Object, template: []ck.CK_ATTRIBUTE) bool {
for (template) |a| {
const have = obj.get(a.type) orelse return false;
if (!std.mem.eql(u8, have, attrBytes(a))) return false;
}
return true;
}
fn readOnlyAttr(t: ck.CK_ATTRIBUTE_TYPE) bool {
return switch (t) {
ck.CKA_CLASS,
ck.CKA_TOKEN,
ck.CKA_PRIVATE,
ck.CKA_KEY_TYPE,
ck.CKA_LOCAL,
ck.CKA_KEY_GEN_MECHANISM,
ck.CKA_ALWAYS_SENSITIVE,
ck.CKA_NEVER_EXTRACTABLE,
ck.CKA_MODULUS,
ck.CKA_PUBLIC_EXPONENT,
ck.CKA_PRIVATE_EXPONENT,
ck.CKA_PRIME_1,
ck.CKA_PRIME_2,
ck.CKA_EXPONENT_1,
ck.CKA_EXPONENT_2,
ck.CKA_COEFFICIENT,
ck.CKA_EC_PARAMS,
ck.CKA_EC_POINT,
=> true,
else => false,
};
}
fn sensitiveProtected(obj: *const Object, t: ck.CK_ATTRIBUTE_TYPE) bool {
const secret_material = switch (t) {
ck.CKA_VALUE,
ck.CKA_PRIVATE_EXPONENT,
ck.CKA_PRIME_1,
ck.CKA_PRIME_2,
ck.CKA_EXPONENT_1,
ck.CKA_EXPONENT_2,
ck.CKA_COEFFICIENT,
=> true,
else => false,
};
if (!secret_material) return false;
if (!obj.has(t)) return false;
if (obj.getBool(ck.CKA_SENSITIVE)) return true;
return obj.has(ck.CKA_EXTRACTABLE) and !obj.getBool(ck.CKA_EXTRACTABLE);
}
pub fn materializeDefaults(obj: *Object, allocator: std.mem.Allocator, class: ck.CK_OBJECT_CLASS) !void {
if (!obj.has(ck.CKA_TOKEN)) try obj.set(allocator, ck.CKA_TOKEN, &[_]u8{ck.CK_FALSE});
if (!obj.has(ck.CKA_PRIVATE)) {
const def: u8 = if (class == ck.CKO_PRIVATE_KEY) ck.CK_TRUE else ck.CK_FALSE;
try obj.set(allocator, ck.CKA_PRIVATE, &[_]u8{def});
}
}
pub fn insertNew(inst: *state.Instance, sess: *session.Session, obj_in: Object, phObject: *ck.CK_OBJECT_HANDLE) ck.CK_RV {
var obj = obj_in;
const allocator = inst.allocator();
const is_token = obj.isToken();
if (is_token and (sess.flags & ck.CKF_RW_SESSION) == 0) {
obj.deinit(allocator);
return ck.CKR_SESSION_READ_ONLY;
}
if (obj.isPrivate() and inst.logged_in != ck.CKU_USER) {
obj.deinit(allocator);
return ck.CKR_USER_NOT_LOGGED_IN;
}
if (is_token and inst.mk == null and obj.hasSealable()) {
obj.deinit(allocator);
return ck.CKR_USER_NOT_LOGGED_IN;
}
const h = inst.objects.insert(obj) orelse {
obj.deinit(allocator);
return ck.CKR_DEVICE_MEMORY;
};
phObject.* = h;
persistIfToken(inst, is_token);
return ck.CKR_OK;
}
fn worse(cur: ck.CK_RV, new: ck.CK_RV) ck.CK_RV {
if (cur == ck.CKR_OK) return new;
if (cur == ck.CKR_ATTRIBUTE_SENSITIVE or new == ck.CKR_ATTRIBUTE_SENSITIVE) return ck.CKR_ATTRIBUTE_SENSITIVE;
if (cur == ck.CKR_ATTRIBUTE_TYPE_INVALID or new == ck.CKR_ATTRIBUTE_TYPE_INVALID) return ck.CKR_ATTRIBUTE_TYPE_INVALID;
return new;
}
fn persistIfToken(inst: *state.Instance, is_token: bool) void {
if (is_token) object_store.save(inst.io(), inst.allocator(), &inst.objects, inst.mk) catch {};
}
pub fn C_CreateObject(hSession: ck.CK_SESSION_HANDLE, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG, phObject: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const allocator = inst.allocator();
const template = inputTemplate(pTemplate, ulCount);
var obj: Object = .{};
var moved = false;
defer if (!moved) obj.deinit(allocator);
for (template) |a| {
obj.set(allocator, a.type, attrBytes(a)) catch |e| return mapSetErr(e);
}
if (!obj.has(ck.CKA_CLASS)) return ck.CKR_TEMPLATE_INCOMPLETE;
const class = ulongAttr(&obj, ck.CKA_CLASS) orelse return ck.CKR_ATTRIBUTE_VALUE_INVALID;
materializeDefaults(&obj, allocator, class) catch |e| return mapSetErr(e);
moved = true;
return insertNew(inst, sess, obj, phObject);
}
pub fn C_CopyObject(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG, phNewObject: *ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const src = inst.objects.getPtr(hObject) orelse return ck.CKR_OBJECT_HANDLE_INVALID;
if (!visible(src, inst.logged_in)) return ck.CKR_OBJECT_HANDLE_INVALID;
const allocator = inst.allocator();
var obj = src.clone(allocator) catch |e| return mapSetErr(e);
var inserted = false;
defer if (!inserted) obj.deinit(allocator);
const template = inputTemplate(pTemplate, ulCount);
for (template) |a| {
if (readOnlyAttr(a.type) and a.type != ck.CKA_TOKEN and a.type != ck.CKA_PRIVATE) return ck.CKR_ATTRIBUTE_READ_ONLY;
}
for (template) |a| {
obj.set(allocator, a.type, attrBytes(a)) catch |e| return mapSetErr(e);
}
const is_token = obj.isToken();
if (is_token and (sess.flags & ck.CKF_RW_SESSION) == 0) return ck.CKR_SESSION_READ_ONLY;
if (obj.isPrivate() and inst.logged_in != ck.CKU_USER) return ck.CKR_USER_NOT_LOGGED_IN;
const h = inst.objects.insert(obj) orelse return ck.CKR_DEVICE_MEMORY;
inserted = true;
phNewObject.* = h;
persistIfToken(inst, is_token);
return ck.CKR_OK;
}
pub fn C_DestroyObject(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const obj = inst.objects.getPtr(hObject) orelse return ck.CKR_OBJECT_HANDLE_INVALID;
if (!visible(obj, inst.logged_in)) return ck.CKR_OBJECT_HANDLE_INVALID;
const was_token = obj.isToken();
if (was_token and (sess.flags & ck.CKF_RW_SESSION) == 0) return ck.CKR_SESSION_READ_ONLY;
if (obj.has(ck.CKA_DESTROYABLE) and !obj.getBool(ck.CKA_DESTROYABLE)) return ck.CKR_ACTION_PROHIBITED;
_ = inst.objects.destroy(inst.allocator(), hObject);
persistIfToken(inst, was_token);
return ck.CKR_OK;
}
pub fn C_GetObjectSize(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE, pulSize: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
_ = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const obj = inst.objects.getPtr(hObject) orelse return ck.CKR_OBJECT_HANDLE_INVALID;
if (!visible(obj, inst.logged_in)) return ck.CKR_OBJECT_HANDLE_INVALID;
pulSize.* = obj.sizeBytes();
return ck.CKR_OK;
}
pub fn C_GetAttributeValue(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
_ = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const obj = inst.objects.getPtr(hObject) orelse return ck.CKR_OBJECT_HANDLE_INVALID;
if (!visible(obj, inst.logged_in)) return ck.CKR_OBJECT_HANDLE_INVALID;
const template = inputTemplate(pTemplate, ulCount);
var rv: ck.CK_RV = ck.CKR_OK;
for (template) |*a| {
if (sensitiveProtected(obj, a.type)) {
a.ulValueLen = ck.CK_UNAVAILABLE_INFORMATION;
rv = worse(rv, ck.CKR_ATTRIBUTE_SENSITIVE);
continue;
}
const val = obj.get(a.type) orelse {
a.ulValueLen = ck.CK_UNAVAILABLE_INFORMATION;
rv = worse(rv, ck.CKR_ATTRIBUTE_TYPE_INVALID);
continue;
};
if (a.pValue) |ptr| {
if (a.ulValueLen < val.len) {
a.ulValueLen = ck.CK_UNAVAILABLE_INFORMATION;
rv = worse(rv, ck.CKR_BUFFER_TOO_SMALL);
} else {
@memcpy(@as([*]u8, @ptrCast(ptr))[0..val.len], val);
a.ulValueLen = @intCast(val.len);
}
} else {
a.ulValueLen = @intCast(val.len);
}
}
return rv;
}
pub fn C_SetAttributeValue(hSession: ck.CK_SESSION_HANDLE, hObject: ck.CK_OBJECT_HANDLE, pTemplate: [*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
const obj = inst.objects.getPtr(hObject) orelse return ck.CKR_OBJECT_HANDLE_INVALID;
if (!visible(obj, inst.logged_in)) return ck.CKR_OBJECT_HANDLE_INVALID;
if (obj.isToken() and (sess.flags & ck.CKF_RW_SESSION) == 0) return ck.CKR_SESSION_READ_ONLY;
if (obj.has(ck.CKA_MODIFIABLE) and !obj.getBool(ck.CKA_MODIFIABLE)) return ck.CKR_ACTION_PROHIBITED;
const template = inputTemplate(pTemplate, ulCount);
for (template) |a| {
if (readOnlyAttr(a.type)) return ck.CKR_ATTRIBUTE_READ_ONLY;
}
const allocator = inst.allocator();
var staged = obj.clone(allocator) catch |e| return mapSetErr(e);
var swapped = false;
defer if (!swapped) staged.deinit(allocator);
for (template) |a| {
staged.set(allocator, a.type, attrBytes(a)) catch |e| return mapSetErr(e);
}
obj.deinit(allocator);
obj.* = staged;
swapped = true;
persistIfToken(inst, obj.isToken());
return ck.CKR_OK;
}
pub fn C_FindObjectsInit(hSession: ck.CK_SESSION_HANDLE, pTemplate: ?[*]ck.CK_ATTRIBUTE, ulCount: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (sess.find.active) return ck.CKR_OPERATION_ACTIVE;
if (pTemplate == null and ulCount != 0) return ck.CKR_ARGUMENTS_BAD;
const template = inputTemplate(pTemplate, ulCount);
sess.find.count = 0;
sess.find.cursor = 0;
for (&inst.objects.slots) |*slot| {
if (slot.*) |*e| {
if (!visible(&e.obj, inst.logged_in)) continue;
if (matches(&e.obj, template)) {
sess.find.matches[sess.find.count] = e.handle;
sess.find.count += 1;
}
}
}
sess.find.active = true;
return ck.CKR_OK;
}
pub fn C_FindObjects(hSession: ck.CK_SESSION_HANDLE, phObject: [*]ck.CK_OBJECT_HANDLE, ulMaxObjectCount: ck.CK_ULONG, pulObjectCount: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (!sess.find.active) return ck.CKR_OPERATION_NOT_INITIALIZED;
const max: usize = @intCast(ulMaxObjectCount);
var n: usize = 0;
while (n < max and sess.find.cursor < sess.find.count) : (n += 1) {
phObject[n] = sess.find.matches[sess.find.cursor];
sess.find.cursor += 1;
}
pulObjectCount.* = @intCast(n);
return ck.CKR_OK;
}
pub fn C_FindObjectsFinal(hSession: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const sess = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
if (!sess.find.active) return ck.CKR_OPERATION_NOT_INITIALIZED;
sess.find.active = false;
sess.find.count = 0;
sess.find.cursor = 0;
return ck.CKR_OK;
}

View File

@ -0,0 +1,23 @@
// ©AngelaMos | 2026
// random.zig
const ck = @import("../ck.zig");
const state = @import("../core/state.zig");
pub fn C_SeedRandom(hSession: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
if (inst.sessions.get(hSession) == null) return ck.CKR_SESSION_HANDLE_INVALID;
return ck.CKR_RANDOM_SEED_NOT_SUPPORTED;
}
pub fn C_GenerateRandom(hSession: ck.CK_SESSION_HANDLE, pRandomData: [*]ck.CK_BYTE, ulRandomLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
if (inst.sessions.get(hSession) == null) return ck.CKR_SESSION_HANDLE_INVALID;
if (ulRandomLen == 0) return ck.CKR_OK;
inst.io().randomSecure(pRandomData[0..@intCast(ulRandomLen)]) catch return ck.CKR_FUNCTION_FAILED;
return ck.CKR_OK;
}

View File

@ -0,0 +1,193 @@
// ©AngelaMos | 2026
// session.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const state = @import("../core/state.zig");
const pin = @import("../crypto/pin.zig");
const keystore = @import("../crypto/keystore.zig");
const token = @import("../core/token.zig");
const object_store = @import("../core/object_store.zig");
fn pinSlice(p: ?[*]ck.CK_UTF8CHAR, len: ck.CK_ULONG) []const u8 {
return if (p) |ptr| ptr[0..@intCast(len)] else &.{};
}
fn sessionState(flags: ck.CK_FLAGS, logged_in: ?ck.CK_USER_TYPE) ck.CK_STATE {
const rw = (flags & ck.CKF_RW_SESSION) != 0;
if (logged_in) |u| {
if (u == ck.CKU_SO) return ck.CKS_RW_SO_FUNCTIONS;
return if (rw) ck.CKS_RW_USER_FUNCTIONS else ck.CKS_RO_USER_FUNCTIONS;
}
return if (rw) ck.CKS_RW_PUBLIC_SESSION else ck.CKS_RO_PUBLIC_SESSION;
}
pub fn C_OpenSession(slotID: ck.CK_SLOT_ID, flags: ck.CK_FLAGS, pApplication: ?*anyopaque, notify: ck.CK_NOTIFY, phSession: *ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
_ = pApplication;
_ = notify;
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
if ((flags & ck.CKF_SERIAL_SESSION) == 0) return ck.CKR_SESSION_PARALLEL_NOT_SUPPORTED;
state.mutex.lock();
defer state.mutex.unlock();
if ((flags & ck.CKF_RW_SESSION) == 0 and inst.logged_in == ck.CKU_SO) {
return ck.CKR_SESSION_READ_WRITE_SO_EXISTS;
}
const h = inst.sessions.open(slotID, flags) orelse return ck.CKR_SESSION_COUNT;
phSession.* = h;
return ck.CKR_OK;
}
pub fn C_CloseSession(hSession: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
if (!inst.sessions.close(hSession)) return ck.CKR_SESSION_HANDLE_INVALID;
if (!inst.sessions.anyOpen()) {
inst.relock();
inst.logged_in = null;
}
return ck.CKR_OK;
}
pub fn C_CloseAllSessions(slotID: ck.CK_SLOT_ID) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
state.mutex.lock();
defer state.mutex.unlock();
inst.sessions.closeAll(slotID);
inst.relock();
inst.logged_in = null;
return ck.CKR_OK;
}
pub fn C_GetSessionInfo(hSession: ck.CK_SESSION_HANDLE, pInfo: *ck.CK_SESSION_INFO) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
const s = inst.sessions.get(hSession) orelse return ck.CKR_SESSION_HANDLE_INVALID;
pInfo.* = .{
.slotID = s.slot,
.state = sessionState(s.flags, inst.logged_in),
.flags = s.flags,
.ulDeviceError = 0,
};
return ck.CKR_OK;
}
pub fn C_GetOperationState(_: ck.CK_SESSION_HANDLE, _: ?[*]ck.CK_BYTE, _: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_SetOperationState(_: ck.CK_SESSION_HANDLE, _: [*]ck.CK_BYTE, _: ck.CK_ULONG, _: ck.CK_OBJECT_HANDLE, _: ck.CK_OBJECT_HANDLE) callconv(.c) ck.CK_RV {
return ck.CKR_FUNCTION_NOT_SUPPORTED;
}
pub fn C_Login(hSession: ck.CK_SESSION_HANDLE, userType: ck.CK_USER_TYPE, pPin: ?[*]ck.CK_UTF8CHAR, ulPinLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (userType != ck.CKU_SO and userType != ck.CKU_USER) return ck.CKR_USER_TYPE_INVALID;
state.mutex.lock();
if (inst.sessions.get(hSession) == null) {
state.mutex.unlock();
return ck.CKR_SESSION_HANDLE_INVALID;
}
if (inst.logged_in != null) {
state.mutex.unlock();
return ck.CKR_USER_ALREADY_LOGGED_IN;
}
var salt: pin.Salt = undefined;
var hash: pin.Hash = undefined;
var wrapped_mk: ?keystore.Wrapped = null;
if (userType == ck.CKU_SO) {
if (inst.sessions.count() > inst.sessions.countRw()) {
state.mutex.unlock();
return ck.CKR_SESSION_READ_ONLY_EXISTS;
}
if (!inst.token.initialized) {
state.mutex.unlock();
return ck.CKR_USER_PIN_NOT_INITIALIZED;
}
if (inst.token.so_fail >= config.login_max_attempts) {
state.mutex.unlock();
return ck.CKR_PIN_LOCKED;
}
salt = inst.token.so.salt;
hash = inst.token.so.hash;
} else {
const u = inst.token.user orelse {
state.mutex.unlock();
return ck.CKR_USER_PIN_NOT_INITIALIZED;
};
if (inst.token.user_fail >= config.login_max_attempts) {
state.mutex.unlock();
return ck.CKR_PIN_LOCKED;
}
salt = u.salt;
hash = u.hash;
wrapped_mk = inst.token.user_mk;
}
const gen = state.cryptoBegin();
const io = inst.io();
const allocator = inst.allocator();
state.mutex.unlock();
defer std.crypto.secureZero(u8, &hash);
const ok = pin.verify(io, allocator, pinSlice(pPin, ulPinLen), &salt, &hash) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
var mk: keystore.MasterKey = undefined;
defer std.crypto.secureZero(u8, &mk);
var have_mk = false;
if (ok and userType == ck.CKU_USER) {
if (wrapped_mk) |w| {
have_mk = keystore.unwrap(io, allocator, pinSlice(pPin, ulPinLen), &w, &mk) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
if (!have_mk) {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
}
}
}
state.mutex.lock();
defer state.mutex.unlock();
state.cryptoEnd();
if (state.currentGeneration() != gen) return ck.CKR_FUNCTION_FAILED;
if (inst.sessions.get(hSession) == null) return ck.CKR_SESSION_HANDLE_INVALID;
if (inst.logged_in != null) return ck.CKR_USER_ALREADY_LOGGED_IN;
if (ok) {
if (userType == ck.CKU_SO) inst.token.so_fail = 0 else inst.token.user_fail = 0;
inst.logged_in = userType;
if (have_mk) {
inst.mk = mk;
object_store.unlock(allocator, &inst.objects, mk) catch {
inst.relock();
inst.logged_in = null;
return ck.CKR_FUNCTION_FAILED;
};
}
token.save(inst.io(), inst.token) catch {};
return ck.CKR_OK;
}
if (userType == ck.CKU_SO) inst.token.so_fail += 1 else inst.token.user_fail += 1;
token.save(inst.io(), inst.token) catch {};
return ck.CKR_PIN_INCORRECT;
}
pub fn C_Logout(hSession: ck.CK_SESSION_HANDLE) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
defer state.mutex.unlock();
if (inst.sessions.get(hSession) == null) return ck.CKR_SESSION_HANDLE_INVALID;
if (inst.logged_in == null) return ck.CKR_USER_NOT_LOGGED_IN;
inst.relock();
inst.logged_in = null;
return ck.CKR_OK;
}

View File

@ -0,0 +1,404 @@
// ©AngelaMos | 2026
// slot_token.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const util = @import("../util.zig");
const state = @import("../core/state.zig");
const pin = @import("../crypto/pin.zig");
const keystore = @import("../crypto/keystore.zig");
const token = @import("../core/token.zig");
const object_store = @import("../core/object_store.zig");
fn requireInit() ?ck.CK_RV {
return if (state.isInitialized()) null else ck.CKR_CRYPTOKI_NOT_INITIALIZED;
}
fn pinSlice(p: ?[*]ck.CK_UTF8CHAR, len: ck.CK_ULONG) []const u8 {
return if (p) |ptr| ptr[0..@intCast(len)] else &.{};
}
fn labelFrom(p: ?[*]ck.CK_UTF8CHAR) [config.label_len]u8 {
var out: [config.label_len]u8 = @splat(' ');
if (p) |lp| @memcpy(&out, lp[0..config.label_len]);
return out;
}
fn pinStateFlags(fail: u32, low: ck.CK_FLAGS, final_try: ck.CK_FLAGS, locked: ck.CK_FLAGS) ck.CK_FLAGS {
const f: ck.CK_ULONG = fail;
const max = config.login_max_attempts;
if (f >= max) return locked;
if (f == max - 1) return final_try;
if (f > 0) return low;
return 0;
}
pub fn C_GetSlotList(_: ck.CK_BBOOL, pSlotList: ?[*]ck.CK_SLOT_ID, pulCount: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
if (requireInit()) |rv| return rv;
if (pSlotList == null) {
pulCount.* = config.slot_count;
return ck.CKR_OK;
}
if (pulCount.* < config.slot_count) {
pulCount.* = config.slot_count;
return ck.CKR_BUFFER_TOO_SMALL;
}
pSlotList.?[0] = config.slot_id;
pulCount.* = config.slot_count;
return ck.CKR_OK;
}
pub fn C_GetSlotInfo(slotID: ck.CK_SLOT_ID, pInfo: *ck.CK_SLOT_INFO) callconv(.c) ck.CK_RV {
if (requireInit()) |rv| return rv;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
pInfo.* = .{
.slotDescription = util.padded(64, config.slot_description),
.manufacturerID = util.padded(32, config.manufacturer_id),
.flags = ck.CKF_TOKEN_PRESENT | ck.CKF_HW_SLOT,
.hardwareVersion = config.hardware_version,
.firmwareVersion = config.firmware_version,
};
return ck.CKR_OK;
}
pub fn C_GetTokenInfo(slotID: ck.CK_SLOT_ID, pInfo: *ck.CK_TOKEN_INFO) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
state.mutex.lock();
defer state.mutex.unlock();
var flags: ck.CK_FLAGS = ck.CKF_RNG | ck.CKF_LOGIN_REQUIRED;
if (inst.token.initialized) flags |= ck.CKF_TOKEN_INITIALIZED;
if (inst.token.user != null) flags |= ck.CKF_USER_PIN_INITIALIZED;
flags |= pinStateFlags(inst.token.user_fail, ck.CKF_USER_PIN_COUNT_LOW, ck.CKF_USER_PIN_FINAL_TRY, ck.CKF_USER_PIN_LOCKED);
flags |= pinStateFlags(inst.token.so_fail, ck.CKF_SO_PIN_COUNT_LOW, ck.CKF_SO_PIN_FINAL_TRY, ck.CKF_SO_PIN_LOCKED);
pInfo.* = .{
.label = if (inst.token.initialized) inst.token.label else util.padded(32, config.token_label),
.manufacturerID = util.padded(32, config.manufacturer_id),
.model = util.padded(16, config.token_model),
.serialNumber = util.padded(16, config.token_serial),
.flags = flags,
.ulMaxSessionCount = config.max_sessions,
.ulSessionCount = inst.sessions.count(),
.ulMaxRwSessionCount = config.max_sessions,
.ulRwSessionCount = inst.sessions.countRw(),
.ulMaxPinLen = config.max_pin_len,
.ulMinPinLen = config.min_pin_len,
.ulTotalPublicMemory = ck.CK_UNAVAILABLE_INFORMATION,
.ulFreePublicMemory = ck.CK_UNAVAILABLE_INFORMATION,
.ulTotalPrivateMemory = ck.CK_UNAVAILABLE_INFORMATION,
.ulFreePrivateMemory = ck.CK_UNAVAILABLE_INFORMATION,
.hardwareVersion = config.hardware_version,
.firmwareVersion = config.firmware_version,
.utcTime = util.padded(16, ""),
};
return ck.CKR_OK;
}
pub fn C_GetMechanismList(slotID: ck.CK_SLOT_ID, pMechanismList: ?[*]ck.CK_MECHANISM_TYPE, pulCount: *ck.CK_ULONG) callconv(.c) ck.CK_RV {
if (requireInit()) |rv| return rv;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
const n: ck.CK_ULONG = config.supported_mechanisms.len;
if (pMechanismList == null) {
pulCount.* = n;
return ck.CKR_OK;
}
if (pulCount.* < n) {
pulCount.* = n;
return ck.CKR_BUFFER_TOO_SMALL;
}
for (config.supported_mechanisms, 0..) |m, i| pMechanismList.?[i] = m;
pulCount.* = n;
return ck.CKR_OK;
}
pub fn C_GetMechanismInfo(slotID: ck.CK_SLOT_ID, mechType: ck.CK_MECHANISM_TYPE, pInfo: *ck.CK_MECHANISM_INFO) callconv(.c) ck.CK_RV {
if (requireInit()) |rv| return rv;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
pInfo.* = switch (mechType) {
ck.CKM_SHA256, ck.CKM_SHA384, ck.CKM_SHA512 => .{
.ulMinKeySize = 0,
.ulMaxKeySize = 0,
.flags = ck.CKF_DIGEST,
},
ck.CKM_SHA256_HMAC, ck.CKM_SHA384_HMAC, ck.CKM_SHA512_HMAC => .{
.ulMinKeySize = config.hmac_min_key_bytes,
.ulMaxKeySize = config.hmac_max_key_bytes,
.flags = ck.CKF_SIGN | ck.CKF_VERIFY,
},
ck.CKM_AES_KEY_GEN => .{
.ulMinKeySize = config.aes_min_key_bytes,
.ulMaxKeySize = config.aes_max_key_bytes,
.flags = ck.CKF_GENERATE,
},
ck.CKM_AES_CBC, ck.CKM_AES_CBC_PAD, ck.CKM_AES_GCM => .{
.ulMinKeySize = config.aes_min_key_bytes,
.ulMaxKeySize = config.aes_max_key_bytes,
.flags = ck.CKF_ENCRYPT | ck.CKF_DECRYPT,
},
ck.CKM_EC_KEY_PAIR_GEN => .{
.ulMinKeySize = config.ec_min_key_bits,
.ulMaxKeySize = config.ec_max_key_bits,
.flags = ck.CKF_GENERATE_KEY_PAIR | ck.CKF_EC_NAMEDCURVE,
},
ck.CKM_ECDSA, ck.CKM_ECDSA_SHA256 => .{
.ulMinKeySize = config.ec_min_key_bits,
.ulMaxKeySize = config.ec_max_key_bits,
.flags = ck.CKF_SIGN | ck.CKF_VERIFY | ck.CKF_EC_NAMEDCURVE,
},
ck.CKM_RSA_PKCS_KEY_PAIR_GEN => .{
.ulMinKeySize = config.rsa_min_key_bits,
.ulMaxKeySize = config.rsa_max_key_bits,
.flags = ck.CKF_GENERATE_KEY_PAIR,
},
ck.CKM_RSA_PKCS => .{
.ulMinKeySize = config.rsa_min_key_bits,
.ulMaxKeySize = config.rsa_max_key_bits,
.flags = ck.CKF_SIGN | ck.CKF_VERIFY | ck.CKF_ENCRYPT | ck.CKF_DECRYPT,
},
ck.CKM_SHA256_RSA_PKCS, ck.CKM_RSA_PKCS_PSS, ck.CKM_SHA256_RSA_PKCS_PSS => .{
.ulMinKeySize = config.rsa_min_key_bits,
.ulMaxKeySize = config.rsa_max_key_bits,
.flags = ck.CKF_SIGN | ck.CKF_VERIFY,
},
ck.CKM_RSA_PKCS_OAEP => .{
.ulMinKeySize = config.rsa_min_key_bits,
.ulMaxKeySize = config.rsa_max_key_bits,
.flags = ck.CKF_ENCRYPT | ck.CKF_DECRYPT,
},
else => return ck.CKR_MECHANISM_INVALID,
};
return ck.CKR_OK;
}
pub fn C_InitToken(slotID: ck.CK_SLOT_ID, pPin: ?[*]ck.CK_UTF8CHAR, ulPinLen: ck.CK_ULONG, pLabel: ?[*]ck.CK_UTF8CHAR) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
if (slotID != config.slot_id) return ck.CKR_SLOT_ID_INVALID;
state.mutex.lock();
if (inst.sessions.anyOpen()) {
state.mutex.unlock();
return ck.CKR_SESSION_EXISTS;
}
const was_init = inst.token.initialized;
if (was_init and inst.token.so_fail >= config.login_max_attempts) {
state.mutex.unlock();
return ck.CKR_PIN_LOCKED;
}
var snap_so = inst.token.so;
const gen = state.cryptoBegin();
const io = inst.io();
const allocator = inst.allocator();
state.mutex.unlock();
defer std.crypto.secureZero(u8, &snap_so.hash);
const new_label = labelFrom(pLabel);
if (was_init) {
const ok = pin.verify(io, allocator, pinSlice(pPin, ulPinLen), &snap_so.salt, &snap_so.hash) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
state.mutex.lock();
defer state.mutex.unlock();
state.cryptoEnd();
if (state.currentGeneration() != gen) return ck.CKR_FUNCTION_FAILED;
if (!ok) {
inst.token.so_fail += 1;
token.save(inst.io(), inst.token) catch {};
return ck.CKR_PIN_INCORRECT;
}
inst.token.user = null;
inst.token.user_mk = null;
inst.wipeMasterKey();
inst.token.user_fail = 0;
inst.token.so_fail = 0;
inst.token.label = new_label;
inst.logged_in = null;
state.bumpGeneration();
inst.objects.clear(inst.allocator());
token.save(inst.io(), inst.token) catch {};
object_store.save(inst.io(), inst.allocator(), &inst.objects, inst.mk) catch {};
return ck.CKR_OK;
}
var salt: pin.Salt = undefined;
pin.genSalt(io, &salt) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
var hash: pin.Hash = undefined;
defer std.crypto.secureZero(u8, &hash);
pin.derive(io, allocator, pinSlice(pPin, ulPinLen), &salt, &hash) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
state.mutex.lock();
defer state.mutex.unlock();
state.cryptoEnd();
inst.token.initialized = true;
inst.token.so = .{ .salt = salt, .hash = hash };
inst.token.user = null;
inst.token.user_mk = null;
inst.wipeMasterKey();
inst.token.so_fail = 0;
inst.token.user_fail = 0;
inst.token.label = new_label;
inst.logged_in = null;
state.bumpGeneration();
inst.objects.clear(inst.allocator());
token.save(inst.io(), inst.token) catch {};
object_store.save(inst.io(), inst.allocator(), &inst.objects, inst.mk) catch {};
return ck.CKR_OK;
}
pub fn C_InitPIN(hSession: ck.CK_SESSION_HANDLE, pPin: ?[*]ck.CK_UTF8CHAR, ulPinLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
if (inst.sessions.get(hSession) == null) {
state.mutex.unlock();
return ck.CKR_SESSION_HANDLE_INVALID;
}
if (inst.logged_in != ck.CKU_SO) {
state.mutex.unlock();
return ck.CKR_USER_NOT_LOGGED_IN;
}
_ = state.cryptoBegin();
const io = inst.io();
const allocator = inst.allocator();
state.mutex.unlock();
var salt: pin.Salt = undefined;
pin.genSalt(io, &salt) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
var hash: pin.Hash = undefined;
defer std.crypto.secureZero(u8, &hash);
pin.derive(io, allocator, pinSlice(pPin, ulPinLen), &salt, &hash) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
var mk: keystore.MasterKey = undefined;
defer std.crypto.secureZero(u8, &mk);
keystore.generateMasterKey(io, &mk) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
const wrapped = keystore.wrap(io, allocator, pinSlice(pPin, ulPinLen), &mk) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
state.mutex.lock();
defer state.mutex.unlock();
state.cryptoEnd();
inst.token.user = .{ .salt = salt, .hash = hash };
inst.token.user_mk = wrapped;
inst.token.user_fail = 0;
state.bumpGeneration();
token.save(inst.io(), inst.token) catch {};
return ck.CKR_OK;
}
pub fn C_SetPIN(hSession: ck.CK_SESSION_HANDLE, pOldPin: ?[*]ck.CK_UTF8CHAR, ulOldLen: ck.CK_ULONG, pNewPin: ?[*]ck.CK_UTF8CHAR, ulNewLen: ck.CK_ULONG) callconv(.c) ck.CK_RV {
const inst = state.current() orelse return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
state.mutex.lock();
const sess = inst.sessions.get(hSession) orelse {
state.mutex.unlock();
return ck.CKR_SESSION_HANDLE_INVALID;
};
if ((sess.flags & ck.CKF_RW_SESSION) == 0) {
state.mutex.unlock();
return ck.CKR_SESSION_READ_ONLY;
}
const as_so = inst.logged_in == ck.CKU_SO;
var salt: pin.Salt = undefined;
var hash: pin.Hash = undefined;
var wrapped_mk: ?keystore.Wrapped = null;
if (as_so) {
if (!inst.token.initialized) {
state.mutex.unlock();
return ck.CKR_USER_PIN_NOT_INITIALIZED;
}
salt = inst.token.so.salt;
hash = inst.token.so.hash;
} else {
const u = inst.token.user orelse {
state.mutex.unlock();
return ck.CKR_USER_PIN_NOT_INITIALIZED;
};
salt = u.salt;
hash = u.hash;
wrapped_mk = inst.token.user_mk;
}
const gen = state.cryptoBegin();
const io = inst.io();
const allocator = inst.allocator();
state.mutex.unlock();
defer std.crypto.secureZero(u8, &hash);
const old_ok = pin.verify(io, allocator, pinSlice(pOldPin, ulOldLen), &salt, &hash) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
if (!old_ok) {
state.cryptoAbort();
return ck.CKR_PIN_INCORRECT;
}
var nsalt: pin.Salt = undefined;
pin.genSalt(io, &nsalt) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
var nhash: pin.Hash = undefined;
defer std.crypto.secureZero(u8, &nhash);
pin.derive(io, allocator, pinSlice(pNewPin, ulNewLen), &nsalt, &nhash) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
var new_mk_wrap: ?keystore.Wrapped = null;
if (!as_so) {
if (wrapped_mk) |w| {
var mk: keystore.MasterKey = undefined;
defer std.crypto.secureZero(u8, &mk);
const unwrapped = keystore.unwrap(io, allocator, pinSlice(pOldPin, ulOldLen), &w, &mk) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
if (!unwrapped) {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
}
new_mk_wrap = keystore.rewrap(io, allocator, pinSlice(pNewPin, ulNewLen), &mk) catch {
state.cryptoAbort();
return ck.CKR_FUNCTION_FAILED;
};
}
}
state.mutex.lock();
defer state.mutex.unlock();
state.cryptoEnd();
if (state.currentGeneration() != gen) return ck.CKR_FUNCTION_FAILED;
if (as_so) {
inst.token.so = .{ .salt = nsalt, .hash = nhash };
inst.token.so_fail = 0;
} else {
inst.token.user = .{ .salt = nsalt, .hash = nhash };
if (new_mk_wrap) |w| inst.token.user_mk = w;
inst.token.user_fail = 0;
}
state.bumpGeneration();
token.save(inst.io(), inst.token) catch {};
return ck.CKR_OK;
}

View File

@ -0,0 +1,503 @@
// ©AngelaMos | 2026
// ck.zig
pub const CK_BYTE = u8;
pub const CK_CHAR = u8;
pub const CK_UTF8CHAR = u8;
pub const CK_BBOOL = u8;
pub const CK_ULONG = c_ulong;
pub const CK_LONG = c_long;
pub const CK_FLAGS = CK_ULONG;
pub const CK_RV = CK_ULONG;
pub const CK_SLOT_ID = CK_ULONG;
pub const CK_SESSION_HANDLE = CK_ULONG;
pub const CK_OBJECT_HANDLE = CK_ULONG;
pub const CK_OBJECT_CLASS = CK_ULONG;
pub const CK_KEY_TYPE = CK_ULONG;
pub const CK_MECHANISM_TYPE = CK_ULONG;
pub const CK_ATTRIBUTE_TYPE = CK_ULONG;
pub const CK_RSA_PKCS_MGF_TYPE = CK_ULONG;
pub const CK_RSA_PKCS_OAEP_SOURCE_TYPE = CK_ULONG;
pub const CK_USER_TYPE = CK_ULONG;
pub const CK_STATE = CK_ULONG;
pub const CK_NOTIFICATION = CK_ULONG;
pub const CK_TRUE: CK_BBOOL = 1;
pub const CK_FALSE: CK_BBOOL = 0;
pub const CK_INVALID_HANDLE: CK_ULONG = 0;
pub const CK_UNAVAILABLE_INFORMATION: CK_ULONG = ~@as(CK_ULONG, 0);
pub const CK_EFFECTIVELY_INFINITE: CK_ULONG = 0;
pub const CKR_OK: CK_RV = 0x00000000;
pub const CKR_CANCEL: CK_RV = 0x00000001;
pub const CKR_HOST_MEMORY: CK_RV = 0x00000002;
pub const CKR_SLOT_ID_INVALID: CK_RV = 0x00000003;
pub const CKR_GENERAL_ERROR: CK_RV = 0x00000005;
pub const CKR_FUNCTION_FAILED: CK_RV = 0x00000006;
pub const CKR_ARGUMENTS_BAD: CK_RV = 0x00000007;
pub const CKR_NO_EVENT: CK_RV = 0x00000008;
pub const CKR_NEED_TO_CREATE_THREADS: CK_RV = 0x00000009;
pub const CKR_CANT_LOCK: CK_RV = 0x0000000A;
pub const CKR_ATTRIBUTE_READ_ONLY: CK_RV = 0x00000010;
pub const CKR_ATTRIBUTE_SENSITIVE: CK_RV = 0x00000011;
pub const CKR_ATTRIBUTE_TYPE_INVALID: CK_RV = 0x00000012;
pub const CKR_ATTRIBUTE_VALUE_INVALID: CK_RV = 0x00000013;
pub const CKR_ACTION_PROHIBITED: CK_RV = 0x0000001B;
pub const CKR_DATA_INVALID: CK_RV = 0x00000020;
pub const CKR_DATA_LEN_RANGE: CK_RV = 0x00000021;
pub const CKR_DEVICE_ERROR: CK_RV = 0x00000030;
pub const CKR_DEVICE_MEMORY: CK_RV = 0x00000031;
pub const CKR_DEVICE_REMOVED: CK_RV = 0x00000032;
pub const CKR_ENCRYPTED_DATA_INVALID: CK_RV = 0x00000040;
pub const CKR_ENCRYPTED_DATA_LEN_RANGE: CK_RV = 0x00000041;
pub const CKR_FUNCTION_CANCELED: CK_RV = 0x00000050;
pub const CKR_FUNCTION_NOT_PARALLEL: CK_RV = 0x00000051;
pub const CKR_FUNCTION_NOT_SUPPORTED: CK_RV = 0x00000054;
pub const CKR_KEY_HANDLE_INVALID: CK_RV = 0x00000060;
pub const CKR_KEY_SIZE_RANGE: CK_RV = 0x00000062;
pub const CKR_KEY_TYPE_INCONSISTENT: CK_RV = 0x00000063;
pub const CKR_KEY_FUNCTION_NOT_PERMITTED: CK_RV = 0x00000068;
pub const CKR_KEY_NOT_WRAPPABLE: CK_RV = 0x00000069;
pub const CKR_KEY_UNEXTRACTABLE: CK_RV = 0x0000006A;
pub const CKR_MECHANISM_INVALID: CK_RV = 0x00000070;
pub const CKR_MECHANISM_PARAM_INVALID: CK_RV = 0x00000071;
pub const CKR_OBJECT_HANDLE_INVALID: CK_RV = 0x00000082;
pub const CKR_OPERATION_ACTIVE: CK_RV = 0x00000090;
pub const CKR_OPERATION_NOT_INITIALIZED: CK_RV = 0x00000091;
pub const CKR_PIN_INCORRECT: CK_RV = 0x000000A0;
pub const CKR_PIN_INVALID: CK_RV = 0x000000A1;
pub const CKR_PIN_LEN_RANGE: CK_RV = 0x000000A2;
pub const CKR_PIN_EXPIRED: CK_RV = 0x000000A3;
pub const CKR_PIN_LOCKED: CK_RV = 0x000000A4;
pub const CKR_SESSION_CLOSED: CK_RV = 0x000000B0;
pub const CKR_SESSION_COUNT: CK_RV = 0x000000B1;
pub const CKR_SESSION_HANDLE_INVALID: CK_RV = 0x000000B3;
pub const CKR_SESSION_PARALLEL_NOT_SUPPORTED: CK_RV = 0x000000B4;
pub const CKR_SESSION_READ_ONLY: CK_RV = 0x000000B5;
pub const CKR_SESSION_EXISTS: CK_RV = 0x000000B6;
pub const CKR_SESSION_READ_ONLY_EXISTS: CK_RV = 0x000000B7;
pub const CKR_SESSION_READ_WRITE_SO_EXISTS: CK_RV = 0x000000B8;
pub const CKR_SIGNATURE_INVALID: CK_RV = 0x000000C0;
pub const CKR_SIGNATURE_LEN_RANGE: CK_RV = 0x000000C1;
pub const CKR_TEMPLATE_INCOMPLETE: CK_RV = 0x000000D0;
pub const CKR_TEMPLATE_INCONSISTENT: CK_RV = 0x000000D1;
pub const CKR_TOKEN_NOT_PRESENT: CK_RV = 0x000000E0;
pub const CKR_TOKEN_NOT_RECOGNIZED: CK_RV = 0x000000E1;
pub const CKR_TOKEN_WRITE_PROTECTED: CK_RV = 0x000000E2;
pub const CKR_USER_ALREADY_LOGGED_IN: CK_RV = 0x00000100;
pub const CKR_USER_NOT_LOGGED_IN: CK_RV = 0x00000101;
pub const CKR_USER_PIN_NOT_INITIALIZED: CK_RV = 0x00000102;
pub const CKR_USER_TYPE_INVALID: CK_RV = 0x00000103;
pub const CKR_USER_ANOTHER_ALREADY_LOGGED_IN: CK_RV = 0x00000104;
pub const CKR_USER_TOO_MANY_TYPES: CK_RV = 0x00000105;
pub const CKR_RANDOM_SEED_NOT_SUPPORTED: CK_RV = 0x00000120;
pub const CKR_RANDOM_NO_RNG: CK_RV = 0x00000121;
pub const CKR_DOMAIN_PARAMS_INVALID: CK_RV = 0x00000130;
pub const CKR_BUFFER_TOO_SMALL: CK_RV = 0x00000150;
pub const CKR_SAVED_STATE_INVALID: CK_RV = 0x00000160;
pub const CKR_INFORMATION_SENSITIVE: CK_RV = 0x00000170;
pub const CKR_STATE_UNSAVEABLE: CK_RV = 0x00000180;
pub const CKR_CRYPTOKI_NOT_INITIALIZED: CK_RV = 0x00000190;
pub const CKR_CRYPTOKI_ALREADY_INITIALIZED: CK_RV = 0x00000191;
pub const CKR_MUTEX_BAD: CK_RV = 0x000001A0;
pub const CKR_MUTEX_NOT_LOCKED: CK_RV = 0x000001A1;
pub const CKR_FUNCTION_REJECTED: CK_RV = 0x00000200;
pub const CKU_SO: CK_USER_TYPE = 0;
pub const CKU_USER: CK_USER_TYPE = 1;
pub const CKU_CONTEXT_SPECIFIC: CK_USER_TYPE = 2;
pub const CKS_RO_PUBLIC_SESSION: CK_STATE = 0;
pub const CKS_RO_USER_FUNCTIONS: CK_STATE = 1;
pub const CKS_RW_PUBLIC_SESSION: CK_STATE = 2;
pub const CKS_RW_USER_FUNCTIONS: CK_STATE = 3;
pub const CKS_RW_SO_FUNCTIONS: CK_STATE = 4;
pub const CKO_DATA: CK_OBJECT_CLASS = 0x00000000;
pub const CKO_CERTIFICATE: CK_OBJECT_CLASS = 0x00000001;
pub const CKO_PUBLIC_KEY: CK_OBJECT_CLASS = 0x00000002;
pub const CKO_PRIVATE_KEY: CK_OBJECT_CLASS = 0x00000003;
pub const CKO_SECRET_KEY: CK_OBJECT_CLASS = 0x00000004;
pub const CKO_HW_FEATURE: CK_OBJECT_CLASS = 0x00000005;
pub const CKO_DOMAIN_PARAMETERS: CK_OBJECT_CLASS = 0x00000006;
pub const CKO_MECHANISM: CK_OBJECT_CLASS = 0x00000007;
pub const CKK_RSA: CK_KEY_TYPE = 0x00000000;
pub const CKK_DSA: CK_KEY_TYPE = 0x00000001;
pub const CKK_DH: CK_KEY_TYPE = 0x00000002;
pub const CKK_EC: CK_KEY_TYPE = 0x00000003;
pub const CKK_GENERIC_SECRET: CK_KEY_TYPE = 0x00000010;
pub const CKK_AES: CK_KEY_TYPE = 0x0000001F;
pub const CKK_SHA256_HMAC: CK_KEY_TYPE = 0x0000002B;
pub const CKK_SHA384_HMAC: CK_KEY_TYPE = 0x0000002C;
pub const CKK_SHA512_HMAC: CK_KEY_TYPE = 0x0000002D;
pub const CKM_RSA_PKCS_KEY_PAIR_GEN: CK_MECHANISM_TYPE = 0x00000000;
pub const CKM_RSA_PKCS: CK_MECHANISM_TYPE = 0x00000001;
pub const CKM_RSA_PKCS_OAEP: CK_MECHANISM_TYPE = 0x00000009;
pub const CKM_RSA_PKCS_PSS: CK_MECHANISM_TYPE = 0x0000000D;
pub const CKM_SHA256_RSA_PKCS: CK_MECHANISM_TYPE = 0x00000040;
pub const CKM_SHA384_RSA_PKCS: CK_MECHANISM_TYPE = 0x00000041;
pub const CKM_SHA512_RSA_PKCS: CK_MECHANISM_TYPE = 0x00000042;
pub const CKM_SHA256_RSA_PKCS_PSS: CK_MECHANISM_TYPE = 0x00000043;
pub const CKM_SHA384_RSA_PKCS_PSS: CK_MECHANISM_TYPE = 0x00000044;
pub const CKM_SHA512_RSA_PKCS_PSS: CK_MECHANISM_TYPE = 0x00000045;
pub const CKM_SHA256: CK_MECHANISM_TYPE = 0x00000250;
pub const CKM_SHA256_HMAC: CK_MECHANISM_TYPE = 0x00000251;
pub const CKM_SHA384: CK_MECHANISM_TYPE = 0x00000260;
pub const CKM_SHA384_HMAC: CK_MECHANISM_TYPE = 0x00000261;
pub const CKM_SHA512: CK_MECHANISM_TYPE = 0x00000270;
pub const CKM_SHA512_HMAC: CK_MECHANISM_TYPE = 0x00000271;
pub const CKM_EC_KEY_PAIR_GEN: CK_MECHANISM_TYPE = 0x00001040;
pub const CKM_ECDSA: CK_MECHANISM_TYPE = 0x00001041;
pub const CKM_ECDSA_SHA256: CK_MECHANISM_TYPE = 0x00001044;
pub const CKM_ECDSA_SHA384: CK_MECHANISM_TYPE = 0x00001045;
pub const CKM_ECDSA_SHA512: CK_MECHANISM_TYPE = 0x00001046;
pub const CKM_AES_KEY_GEN: CK_MECHANISM_TYPE = 0x00001080;
pub const CKM_AES_CBC: CK_MECHANISM_TYPE = 0x00001082;
pub const CKM_AES_CBC_PAD: CK_MECHANISM_TYPE = 0x00001085;
pub const CKM_AES_GCM: CK_MECHANISM_TYPE = 0x00001087;
pub const CKA_CLASS: CK_ATTRIBUTE_TYPE = 0x00000000;
pub const CKA_TOKEN: CK_ATTRIBUTE_TYPE = 0x00000001;
pub const CKA_PRIVATE: CK_ATTRIBUTE_TYPE = 0x00000002;
pub const CKA_LABEL: CK_ATTRIBUTE_TYPE = 0x00000003;
pub const CKA_VALUE: CK_ATTRIBUTE_TYPE = 0x00000011;
pub const CKA_CERTIFICATE_TYPE: CK_ATTRIBUTE_TYPE = 0x00000080;
pub const CKA_KEY_TYPE: CK_ATTRIBUTE_TYPE = 0x00000100;
pub const CKA_ID: CK_ATTRIBUTE_TYPE = 0x00000102;
pub const CKA_SENSITIVE: CK_ATTRIBUTE_TYPE = 0x00000103;
pub const CKA_ENCRYPT: CK_ATTRIBUTE_TYPE = 0x00000104;
pub const CKA_DECRYPT: CK_ATTRIBUTE_TYPE = 0x00000105;
pub const CKA_WRAP: CK_ATTRIBUTE_TYPE = 0x00000106;
pub const CKA_UNWRAP: CK_ATTRIBUTE_TYPE = 0x00000107;
pub const CKA_SIGN: CK_ATTRIBUTE_TYPE = 0x00000108;
pub const CKA_SIGN_RECOVER: CK_ATTRIBUTE_TYPE = 0x00000109;
pub const CKA_VERIFY: CK_ATTRIBUTE_TYPE = 0x0000010A;
pub const CKA_VERIFY_RECOVER: CK_ATTRIBUTE_TYPE = 0x0000010B;
pub const CKA_DERIVE: CK_ATTRIBUTE_TYPE = 0x0000010C;
pub const CKA_MODULUS: CK_ATTRIBUTE_TYPE = 0x00000120;
pub const CKA_MODULUS_BITS: CK_ATTRIBUTE_TYPE = 0x00000121;
pub const CKA_PUBLIC_EXPONENT: CK_ATTRIBUTE_TYPE = 0x00000122;
pub const CKA_PRIVATE_EXPONENT: CK_ATTRIBUTE_TYPE = 0x00000123;
pub const CKA_PRIME_1: CK_ATTRIBUTE_TYPE = 0x00000124;
pub const CKA_PRIME_2: CK_ATTRIBUTE_TYPE = 0x00000125;
pub const CKA_EXPONENT_1: CK_ATTRIBUTE_TYPE = 0x00000126;
pub const CKA_EXPONENT_2: CK_ATTRIBUTE_TYPE = 0x00000127;
pub const CKA_COEFFICIENT: CK_ATTRIBUTE_TYPE = 0x00000128;
pub const CKA_VALUE_LEN: CK_ATTRIBUTE_TYPE = 0x00000161;
pub const CKA_EXTRACTABLE: CK_ATTRIBUTE_TYPE = 0x00000162;
pub const CKA_LOCAL: CK_ATTRIBUTE_TYPE = 0x00000163;
pub const CKA_NEVER_EXTRACTABLE: CK_ATTRIBUTE_TYPE = 0x00000164;
pub const CKA_ALWAYS_SENSITIVE: CK_ATTRIBUTE_TYPE = 0x00000165;
pub const CKA_KEY_GEN_MECHANISM: CK_ATTRIBUTE_TYPE = 0x00000166;
pub const CKA_MODIFIABLE: CK_ATTRIBUTE_TYPE = 0x00000170;
pub const CKA_COPYABLE: CK_ATTRIBUTE_TYPE = 0x00000171;
pub const CKA_DESTROYABLE: CK_ATTRIBUTE_TYPE = 0x00000172;
pub const CKA_EC_PARAMS: CK_ATTRIBUTE_TYPE = 0x00000180;
pub const CKA_EC_POINT: CK_ATTRIBUTE_TYPE = 0x00000181;
pub const CKA_ALWAYS_AUTHENTICATE: CK_ATTRIBUTE_TYPE = 0x00000202;
pub const CKF_TOKEN_PRESENT: CK_FLAGS = 0x00000001;
pub const CKF_REMOVABLE_DEVICE: CK_FLAGS = 0x00000002;
pub const CKF_HW_SLOT: CK_FLAGS = 0x00000004;
pub const CKF_RNG: CK_FLAGS = 0x00000001;
pub const CKF_WRITE_PROTECTED: CK_FLAGS = 0x00000002;
pub const CKF_LOGIN_REQUIRED: CK_FLAGS = 0x00000004;
pub const CKF_USER_PIN_INITIALIZED: CK_FLAGS = 0x00000008;
pub const CKF_RESTORE_KEY_NOT_NEEDED: CK_FLAGS = 0x00000020;
pub const CKF_CLOCK_ON_TOKEN: CK_FLAGS = 0x00000040;
pub const CKF_PROTECTED_AUTHENTICATION_PATH: CK_FLAGS = 0x00000100;
pub const CKF_DUAL_CRYPTO_OPERATIONS: CK_FLAGS = 0x00000200;
pub const CKF_TOKEN_INITIALIZED: CK_FLAGS = 0x00000400;
pub const CKF_USER_PIN_COUNT_LOW: CK_FLAGS = 0x00010000;
pub const CKF_USER_PIN_FINAL_TRY: CK_FLAGS = 0x00020000;
pub const CKF_USER_PIN_LOCKED: CK_FLAGS = 0x00040000;
pub const CKF_SO_PIN_COUNT_LOW: CK_FLAGS = 0x00100000;
pub const CKF_SO_PIN_FINAL_TRY: CK_FLAGS = 0x00200000;
pub const CKF_SO_PIN_LOCKED: CK_FLAGS = 0x00400000;
pub const CKF_RW_SESSION: CK_FLAGS = 0x00000002;
pub const CKF_SERIAL_SESSION: CK_FLAGS = 0x00000004;
pub const CKF_HW: CK_FLAGS = 0x00000001;
pub const CKF_ENCRYPT: CK_FLAGS = 0x00000100;
pub const CKF_DECRYPT: CK_FLAGS = 0x00000200;
pub const CKF_DIGEST: CK_FLAGS = 0x00000400;
pub const CKF_SIGN: CK_FLAGS = 0x00000800;
pub const CKF_SIGN_RECOVER: CK_FLAGS = 0x00001000;
pub const CKF_VERIFY: CK_FLAGS = 0x00002000;
pub const CKF_VERIFY_RECOVER: CK_FLAGS = 0x00004000;
pub const CKF_GENERATE: CK_FLAGS = 0x00008000;
pub const CKF_GENERATE_KEY_PAIR: CK_FLAGS = 0x00010000;
pub const CKF_WRAP: CK_FLAGS = 0x00020000;
pub const CKF_UNWRAP: CK_FLAGS = 0x00040000;
pub const CKF_DERIVE: CK_FLAGS = 0x00080000;
pub const CKF_EC_F_P: CK_FLAGS = 0x00100000;
pub const CKF_EC_NAMEDCURVE: CK_FLAGS = 0x00800000;
pub const CKF_EC_UNCOMPRESS: CK_FLAGS = 0x01000000;
pub const CKF_EC_COMPRESS: CK_FLAGS = 0x02000000;
pub const CKF_LIBRARY_CANT_CREATE_OS_THREADS: CK_FLAGS = 0x00000001;
pub const CKF_OS_LOCKING_OK: CK_FLAGS = 0x00000002;
pub const CKG_MGF1_SHA1: CK_RSA_PKCS_MGF_TYPE = 0x00000001;
pub const CKG_MGF1_SHA256: CK_RSA_PKCS_MGF_TYPE = 0x00000002;
pub const CKG_MGF1_SHA384: CK_RSA_PKCS_MGF_TYPE = 0x00000003;
pub const CKG_MGF1_SHA512: CK_RSA_PKCS_MGF_TYPE = 0x00000004;
pub const CKZ_DATA_SPECIFIED: CK_RSA_PKCS_OAEP_SOURCE_TYPE = 0x00000001;
pub const CK_VERSION = extern struct {
major: CK_BYTE,
minor: CK_BYTE,
};
pub const CK_INFO = extern struct {
cryptokiVersion: CK_VERSION,
manufacturerID: [32]CK_UTF8CHAR,
flags: CK_FLAGS,
libraryDescription: [32]CK_UTF8CHAR,
libraryVersion: CK_VERSION,
};
pub const CK_SLOT_INFO = extern struct {
slotDescription: [64]CK_UTF8CHAR,
manufacturerID: [32]CK_UTF8CHAR,
flags: CK_FLAGS,
hardwareVersion: CK_VERSION,
firmwareVersion: CK_VERSION,
};
pub const CK_TOKEN_INFO = extern struct {
label: [32]CK_UTF8CHAR,
manufacturerID: [32]CK_UTF8CHAR,
model: [16]CK_UTF8CHAR,
serialNumber: [16]CK_CHAR,
flags: CK_FLAGS,
ulMaxSessionCount: CK_ULONG,
ulSessionCount: CK_ULONG,
ulMaxRwSessionCount: CK_ULONG,
ulRwSessionCount: CK_ULONG,
ulMaxPinLen: CK_ULONG,
ulMinPinLen: CK_ULONG,
ulTotalPublicMemory: CK_ULONG,
ulFreePublicMemory: CK_ULONG,
ulTotalPrivateMemory: CK_ULONG,
ulFreePrivateMemory: CK_ULONG,
hardwareVersion: CK_VERSION,
firmwareVersion: CK_VERSION,
utcTime: [16]CK_CHAR,
};
pub const CK_SESSION_INFO = extern struct {
slotID: CK_SLOT_ID,
state: CK_STATE,
flags: CK_FLAGS,
ulDeviceError: CK_ULONG,
};
pub const CK_MECHANISM_INFO = extern struct {
ulMinKeySize: CK_ULONG,
ulMaxKeySize: CK_ULONG,
flags: CK_FLAGS,
};
pub const CK_ATTRIBUTE = extern struct {
type: CK_ATTRIBUTE_TYPE,
pValue: ?*anyopaque,
ulValueLen: CK_ULONG,
};
pub const CK_MECHANISM = extern struct {
mechanism: CK_MECHANISM_TYPE,
pParameter: ?*anyopaque,
ulParameterLen: CK_ULONG,
};
pub const CK_GCM_PARAMS = extern struct {
pIv: ?[*]CK_BYTE,
ulIvLen: CK_ULONG,
ulIvBits: CK_ULONG,
pAAD: ?[*]CK_BYTE,
ulAADLen: CK_ULONG,
ulTagBits: CK_ULONG,
};
pub const CK_RSA_PKCS_PSS_PARAMS = extern struct {
hashAlg: CK_MECHANISM_TYPE,
mgf: CK_RSA_PKCS_MGF_TYPE,
sLen: CK_ULONG,
};
pub const CK_RSA_PKCS_OAEP_PARAMS = extern struct {
hashAlg: CK_MECHANISM_TYPE,
mgf: CK_RSA_PKCS_MGF_TYPE,
source: CK_RSA_PKCS_OAEP_SOURCE_TYPE,
pSourceData: ?*anyopaque,
ulSourceDataLen: CK_ULONG,
};
pub const CK_DATE = extern struct {
year: [4]CK_CHAR,
month: [2]CK_CHAR,
day: [2]CK_CHAR,
};
pub const CK_NOTIFY = ?*const fn (CK_SESSION_HANDLE, CK_NOTIFICATION, ?*anyopaque) callconv(.c) CK_RV;
pub const CK_CREATEMUTEX = ?*const fn (*?*anyopaque) callconv(.c) CK_RV;
pub const CK_DESTROYMUTEX = ?*const fn (?*anyopaque) callconv(.c) CK_RV;
pub const CK_LOCKMUTEX = ?*const fn (?*anyopaque) callconv(.c) CK_RV;
pub const CK_UNLOCKMUTEX = ?*const fn (?*anyopaque) callconv(.c) CK_RV;
pub const CK_C_INITIALIZE_ARGS = extern struct {
CreateMutex: CK_CREATEMUTEX,
DestroyMutex: CK_DESTROYMUTEX,
LockMutex: CK_LOCKMUTEX,
UnlockMutex: CK_UNLOCKMUTEX,
flags: CK_FLAGS,
pReserved: ?*anyopaque,
};
pub const CK_C_Initialize = ?*const fn (?*anyopaque) callconv(.c) CK_RV;
pub const CK_C_Finalize = ?*const fn (?*anyopaque) callconv(.c) CK_RV;
pub const CK_C_GetInfo = ?*const fn (*CK_INFO) callconv(.c) CK_RV;
pub const CK_C_GetFunctionList = ?*const fn (*?*CK_FUNCTION_LIST) callconv(.c) CK_RV;
pub const CK_C_GetSlotList = ?*const fn (CK_BBOOL, ?[*]CK_SLOT_ID, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_GetSlotInfo = ?*const fn (CK_SLOT_ID, *CK_SLOT_INFO) callconv(.c) CK_RV;
pub const CK_C_GetTokenInfo = ?*const fn (CK_SLOT_ID, *CK_TOKEN_INFO) callconv(.c) CK_RV;
pub const CK_C_GetMechanismList = ?*const fn (CK_SLOT_ID, ?[*]CK_MECHANISM_TYPE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_GetMechanismInfo = ?*const fn (CK_SLOT_ID, CK_MECHANISM_TYPE, *CK_MECHANISM_INFO) callconv(.c) CK_RV;
pub const CK_C_InitToken = ?*const fn (CK_SLOT_ID, ?[*]CK_UTF8CHAR, CK_ULONG, ?[*]CK_UTF8CHAR) callconv(.c) CK_RV;
pub const CK_C_InitPIN = ?*const fn (CK_SESSION_HANDLE, ?[*]CK_UTF8CHAR, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_SetPIN = ?*const fn (CK_SESSION_HANDLE, ?[*]CK_UTF8CHAR, CK_ULONG, ?[*]CK_UTF8CHAR, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_OpenSession = ?*const fn (CK_SLOT_ID, CK_FLAGS, ?*anyopaque, CK_NOTIFY, *CK_SESSION_HANDLE) callconv(.c) CK_RV;
pub const CK_C_CloseSession = ?*const fn (CK_SESSION_HANDLE) callconv(.c) CK_RV;
pub const CK_C_CloseAllSessions = ?*const fn (CK_SLOT_ID) callconv(.c) CK_RV;
pub const CK_C_GetSessionInfo = ?*const fn (CK_SESSION_HANDLE, *CK_SESSION_INFO) callconv(.c) CK_RV;
pub const CK_C_GetOperationState = ?*const fn (CK_SESSION_HANDLE, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_SetOperationState = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, CK_OBJECT_HANDLE, CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_Login = ?*const fn (CK_SESSION_HANDLE, CK_USER_TYPE, ?[*]CK_UTF8CHAR, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_Logout = ?*const fn (CK_SESSION_HANDLE) callconv(.c) CK_RV;
pub const CK_C_CreateObject = ?*const fn (CK_SESSION_HANDLE, [*]CK_ATTRIBUTE, CK_ULONG, *CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_CopyObject = ?*const fn (CK_SESSION_HANDLE, CK_OBJECT_HANDLE, [*]CK_ATTRIBUTE, CK_ULONG, *CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_DestroyObject = ?*const fn (CK_SESSION_HANDLE, CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_GetObjectSize = ?*const fn (CK_SESSION_HANDLE, CK_OBJECT_HANDLE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_GetAttributeValue = ?*const fn (CK_SESSION_HANDLE, CK_OBJECT_HANDLE, [*]CK_ATTRIBUTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_SetAttributeValue = ?*const fn (CK_SESSION_HANDLE, CK_OBJECT_HANDLE, [*]CK_ATTRIBUTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_FindObjectsInit = ?*const fn (CK_SESSION_HANDLE, ?[*]CK_ATTRIBUTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_FindObjects = ?*const fn (CK_SESSION_HANDLE, [*]CK_OBJECT_HANDLE, CK_ULONG, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_FindObjectsFinal = ?*const fn (CK_SESSION_HANDLE) callconv(.c) CK_RV;
pub const CK_C_EncryptInit = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_Encrypt = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_EncryptUpdate = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_EncryptFinal = ?*const fn (CK_SESSION_HANDLE, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_DecryptInit = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_Decrypt = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_DecryptUpdate = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_DecryptFinal = ?*const fn (CK_SESSION_HANDLE, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_DigestInit = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM) callconv(.c) CK_RV;
pub const CK_C_Digest = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_DigestUpdate = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_DigestKey = ?*const fn (CK_SESSION_HANDLE, CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_DigestFinal = ?*const fn (CK_SESSION_HANDLE, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_SignInit = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_Sign = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_SignUpdate = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_SignFinal = ?*const fn (CK_SESSION_HANDLE, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_SignRecoverInit = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_SignRecover = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_VerifyInit = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_Verify = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, [*]CK_BYTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_VerifyUpdate = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_VerifyFinal = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_VerifyRecoverInit = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_VerifyRecover = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_DigestEncryptUpdate = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_DecryptDigestUpdate = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_SignEncryptUpdate = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_DecryptVerifyUpdate = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_GenerateKey = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, [*]CK_ATTRIBUTE, CK_ULONG, *CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_GenerateKeyPair = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, [*]CK_ATTRIBUTE, CK_ULONG, [*]CK_ATTRIBUTE, CK_ULONG, *CK_OBJECT_HANDLE, *CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_WrapKey = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, CK_OBJECT_HANDLE, CK_OBJECT_HANDLE, ?[*]CK_BYTE, *CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_UnwrapKey = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, CK_OBJECT_HANDLE, [*]CK_BYTE, CK_ULONG, [*]CK_ATTRIBUTE, CK_ULONG, *CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_DeriveKey = ?*const fn (CK_SESSION_HANDLE, *CK_MECHANISM, CK_OBJECT_HANDLE, ?[*]CK_ATTRIBUTE, CK_ULONG, *CK_OBJECT_HANDLE) callconv(.c) CK_RV;
pub const CK_C_SeedRandom = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_GenerateRandom = ?*const fn (CK_SESSION_HANDLE, [*]CK_BYTE, CK_ULONG) callconv(.c) CK_RV;
pub const CK_C_GetFunctionStatus = ?*const fn (CK_SESSION_HANDLE) callconv(.c) CK_RV;
pub const CK_C_CancelFunction = ?*const fn (CK_SESSION_HANDLE) callconv(.c) CK_RV;
pub const CK_C_WaitForSlotEvent = ?*const fn (CK_FLAGS, *CK_SLOT_ID, ?*anyopaque) callconv(.c) CK_RV;
pub const CK_FUNCTION_LIST = extern struct {
version: CK_VERSION,
C_Initialize: CK_C_Initialize,
C_Finalize: CK_C_Finalize,
C_GetInfo: CK_C_GetInfo,
C_GetFunctionList: CK_C_GetFunctionList,
C_GetSlotList: CK_C_GetSlotList,
C_GetSlotInfo: CK_C_GetSlotInfo,
C_GetTokenInfo: CK_C_GetTokenInfo,
C_GetMechanismList: CK_C_GetMechanismList,
C_GetMechanismInfo: CK_C_GetMechanismInfo,
C_InitToken: CK_C_InitToken,
C_InitPIN: CK_C_InitPIN,
C_SetPIN: CK_C_SetPIN,
C_OpenSession: CK_C_OpenSession,
C_CloseSession: CK_C_CloseSession,
C_CloseAllSessions: CK_C_CloseAllSessions,
C_GetSessionInfo: CK_C_GetSessionInfo,
C_GetOperationState: CK_C_GetOperationState,
C_SetOperationState: CK_C_SetOperationState,
C_Login: CK_C_Login,
C_Logout: CK_C_Logout,
C_CreateObject: CK_C_CreateObject,
C_CopyObject: CK_C_CopyObject,
C_DestroyObject: CK_C_DestroyObject,
C_GetObjectSize: CK_C_GetObjectSize,
C_GetAttributeValue: CK_C_GetAttributeValue,
C_SetAttributeValue: CK_C_SetAttributeValue,
C_FindObjectsInit: CK_C_FindObjectsInit,
C_FindObjects: CK_C_FindObjects,
C_FindObjectsFinal: CK_C_FindObjectsFinal,
C_EncryptInit: CK_C_EncryptInit,
C_Encrypt: CK_C_Encrypt,
C_EncryptUpdate: CK_C_EncryptUpdate,
C_EncryptFinal: CK_C_EncryptFinal,
C_DecryptInit: CK_C_DecryptInit,
C_Decrypt: CK_C_Decrypt,
C_DecryptUpdate: CK_C_DecryptUpdate,
C_DecryptFinal: CK_C_DecryptFinal,
C_DigestInit: CK_C_DigestInit,
C_Digest: CK_C_Digest,
C_DigestUpdate: CK_C_DigestUpdate,
C_DigestKey: CK_C_DigestKey,
C_DigestFinal: CK_C_DigestFinal,
C_SignInit: CK_C_SignInit,
C_Sign: CK_C_Sign,
C_SignUpdate: CK_C_SignUpdate,
C_SignFinal: CK_C_SignFinal,
C_SignRecoverInit: CK_C_SignRecoverInit,
C_SignRecover: CK_C_SignRecover,
C_VerifyInit: CK_C_VerifyInit,
C_Verify: CK_C_Verify,
C_VerifyUpdate: CK_C_VerifyUpdate,
C_VerifyFinal: CK_C_VerifyFinal,
C_VerifyRecoverInit: CK_C_VerifyRecoverInit,
C_VerifyRecover: CK_C_VerifyRecover,
C_DigestEncryptUpdate: CK_C_DigestEncryptUpdate,
C_DecryptDigestUpdate: CK_C_DecryptDigestUpdate,
C_SignEncryptUpdate: CK_C_SignEncryptUpdate,
C_DecryptVerifyUpdate: CK_C_DecryptVerifyUpdate,
C_GenerateKey: CK_C_GenerateKey,
C_GenerateKeyPair: CK_C_GenerateKeyPair,
C_WrapKey: CK_C_WrapKey,
C_UnwrapKey: CK_C_UnwrapKey,
C_DeriveKey: CK_C_DeriveKey,
C_SeedRandom: CK_C_SeedRandom,
C_GenerateRandom: CK_C_GenerateRandom,
C_GetFunctionStatus: CK_C_GetFunctionStatus,
C_CancelFunction: CK_C_CancelFunction,
C_WaitForSlotEvent: CK_C_WaitForSlotEvent,
};

View File

@ -0,0 +1,89 @@
// ©AngelaMos | 2026
// config.zig
const ck = @import("ck.zig");
pub const cryptoki_version: ck.CK_VERSION = .{ .major = 2, .minor = 40 };
pub const library_version: ck.CK_VERSION = .{ .major = 0, .minor = 1 };
pub const hardware_version: ck.CK_VERSION = .{ .major = 1, .minor = 0 };
pub const firmware_version: ck.CK_VERSION = .{ .major = 0, .minor = 1 };
pub const manufacturer_id = "Angelamos";
pub const library_description = "Zig HSM Emulator";
pub const slot_description = "AngelaMos HSM Emulator Slot 0";
pub const token_label = "AngelaMos-HSM";
pub const token_model = "hsm-emu";
pub const token_serial = "0000000000000001";
pub const slot_id: ck.CK_SLOT_ID = 0;
pub const slot_count: ck.CK_ULONG = 1;
pub const max_sessions: ck.CK_ULONG = 64;
pub const min_pin_len: ck.CK_ULONG = 4;
pub const max_pin_len: ck.CK_ULONG = 255;
pub const aes_min_key_bytes: ck.CK_ULONG = 16;
pub const aes_max_key_bytes: ck.CK_ULONG = 32;
pub const rsa_min_key_bits: ck.CK_ULONG = 2048;
pub const rsa_max_key_bits: ck.CK_ULONG = 4096;
pub const ec_min_key_bits: ck.CK_ULONG = 256;
pub const ec_max_key_bits: ck.CK_ULONG = 384;
pub const ec_keygen_max_attempts: usize = 8;
pub const hmac_min_key_bytes: ck.CK_ULONG = 32;
pub const hmac_max_key_bytes: ck.CK_ULONG = 64;
pub const pin_kdf_t: u32 = 3;
pub const pin_kdf_m_kib: u32 = 65536;
pub const pin_kdf_p: u24 = 1;
pub const pin_salt_len: usize = 16;
pub const pin_hash_len: usize = 32;
pub const login_max_attempts: ck.CK_ULONG = 3;
pub const token_path_env = "ANGELAMOS_HSM_TOKEN";
pub const token_path_default = ".angelamos-hsm-token";
pub const token_record_magic: u32 = 0x484D5331;
pub const token_record_version: u32 = 2;
pub const path_buf_len: usize = 4096;
pub const token_read_limit: usize = 512;
pub const label_len: usize = 32;
pub const object_path_env = "ANGELAMOS_HSM_OBJECTS";
pub const object_path_default = ".angelamos-hsm-objects";
pub const object_record_magic: u32 = 0x484D4F31;
pub const object_record_version: u32 = 2;
pub const object_read_limit: usize = 4 * 1024 * 1024;
pub const max_objects: usize = 256;
pub const max_attributes_per_object: usize = 64;
pub const max_attr_value_len: usize = 64 * 1024;
pub const aes_block_len: usize = 16;
pub const gcm_iv_len: usize = 12;
pub const gcm_iv_bits: ck.CK_ULONG = 96;
pub const gcm_tag_len: usize = 16;
pub const gcm_tag_bits: ck.CK_ULONG = 128;
pub const max_gcm_aad_len: usize = 256;
pub const master_key_len: usize = 32;
pub const supported_mechanisms = [_]ck.CK_MECHANISM_TYPE{
ck.CKM_SHA256,
ck.CKM_SHA384,
ck.CKM_SHA512,
ck.CKM_SHA256_HMAC,
ck.CKM_SHA384_HMAC,
ck.CKM_SHA512_HMAC,
ck.CKM_AES_KEY_GEN,
ck.CKM_AES_CBC,
ck.CKM_AES_CBC_PAD,
ck.CKM_AES_GCM,
ck.CKM_EC_KEY_PAIR_GEN,
ck.CKM_ECDSA,
ck.CKM_ECDSA_SHA256,
ck.CKM_RSA_PKCS_KEY_PAIR_GEN,
ck.CKM_RSA_PKCS,
ck.CKM_SHA256_RSA_PKCS,
ck.CKM_RSA_PKCS_PSS,
ck.CKM_SHA256_RSA_PKCS_PSS,
ck.CKM_RSA_PKCS_OAEP,
};

View File

@ -0,0 +1,23 @@
// ©AngelaMos | 2026
// env.zig
const std = @import("std");
pub fn get(key: []const u8) ?[]const u8 {
var i: usize = 0;
while (std.c.environ[i]) |entry| : (i += 1) {
const s = std.mem.sliceTo(entry, 0);
if (s.len > key.len and s[key.len] == '=' and std.mem.eql(u8, s[0..key.len], key)) {
return s[key.len + 1 ..];
}
}
return null;
}
pub fn resolvePath(buf: []u8, env_key: []const u8, default_rel: []const u8) ![]const u8 {
if (get(env_key)) |p| {
if (p.len > 0) return p;
}
const home = get("HOME") orelse return error.NoHomeDir;
return std.fmt.bufPrint(buf, "{s}/{s}", .{ home, default_rel });
}

View File

@ -0,0 +1,31 @@
// ©AngelaMos | 2026
// lock.zig
const std = @import("std");
const spin_limit: usize = 64;
pub const Lock = struct {
state: std.atomic.Mutex = .unlocked,
pub fn lock(self: *Lock) void {
var spins: usize = 0;
while (!self.state.tryLock()) {
if (spins < spin_limit) {
spins += 1;
std.atomic.spinLoopHint();
} else {
spins = 0;
std.Thread.yield() catch std.atomic.spinLoopHint();
}
}
}
pub fn tryLock(self: *Lock) bool {
return self.state.tryLock();
}
pub fn unlock(self: *Lock) void {
self.state.unlock();
}
};

View File

@ -0,0 +1,582 @@
// ©AngelaMos | 2026
// object_store.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const env = @import("env.zig");
const keystore = @import("../crypto/keystore.zig");
fn secureFree(allocator: std.mem.Allocator, value: []u8) void {
std.crypto.secureZero(u8, value);
allocator.free(value);
}
pub const Attribute = struct {
type: ck.CK_ATTRIBUTE_TYPE,
value: []u8,
sealed: bool = false,
};
fn isSecretMaterial(t: ck.CK_ATTRIBUTE_TYPE) bool {
return switch (t) {
ck.CKA_VALUE,
ck.CKA_PRIVATE_EXPONENT,
ck.CKA_PRIME_1,
ck.CKA_PRIME_2,
ck.CKA_EXPONENT_1,
ck.CKA_EXPONENT_2,
ck.CKA_COEFFICIENT,
=> true,
else => false,
};
}
pub const Object = struct {
attrs: std.ArrayList(Attribute) = .empty,
pub fn deinit(self: *Object, allocator: std.mem.Allocator) void {
for (self.attrs.items) |a| secureFree(allocator, a.value);
self.attrs.deinit(allocator);
self.* = .{};
}
pub fn findPtr(self: *Object, t: ck.CK_ATTRIBUTE_TYPE) ?*Attribute {
for (self.attrs.items) |*a| {
if (a.type == t) return a;
}
return null;
}
pub fn get(self: *const Object, t: ck.CK_ATTRIBUTE_TYPE) ?[]const u8 {
for (self.attrs.items) |a| {
if (a.type == t) return a.value;
}
return null;
}
pub fn has(self: *const Object, t: ck.CK_ATTRIBUTE_TYPE) bool {
return self.get(t) != null;
}
pub fn getBool(self: *const Object, t: ck.CK_ATTRIBUTE_TYPE) bool {
const v = self.get(t) orelse return false;
return v.len >= 1 and v[0] != ck.CK_FALSE;
}
pub fn isToken(self: *const Object) bool {
return self.getBool(ck.CKA_TOKEN);
}
pub fn isPrivate(self: *const Object) bool {
return self.getBool(ck.CKA_PRIVATE);
}
pub fn shouldSeal(self: *const Object, t: ck.CK_ATTRIBUTE_TYPE) bool {
if (!isSecretMaterial(t)) return false;
if (self.getBool(ck.CKA_SENSITIVE)) return true;
return self.has(ck.CKA_EXTRACTABLE) and !self.getBool(ck.CKA_EXTRACTABLE);
}
pub fn hasSealable(self: *const Object) bool {
for (self.attrs.items) |a| {
if (self.shouldSeal(a.type)) return true;
}
return false;
}
pub fn set(self: *Object, allocator: std.mem.Allocator, t: ck.CK_ATTRIBUTE_TYPE, bytes: []const u8) !void {
if (bytes.len > config.max_attr_value_len) return error.AttrTooLarge;
if (self.findPtr(t)) |a| {
const dup = try allocator.dupe(u8, bytes);
secureFree(allocator, a.value);
a.value = dup;
return;
}
if (self.attrs.items.len >= config.max_attributes_per_object) return error.TooManyAttributes;
const dup = try allocator.dupe(u8, bytes);
errdefer secureFree(allocator, dup);
try self.attrs.append(allocator, .{ .type = t, .value = dup });
}
pub fn sizeBytes(self: *const Object) ck.CK_ULONG {
var total: ck.CK_ULONG = 0;
for (self.attrs.items) |a| total += @intCast(a.value.len);
return total;
}
pub fn clone(self: *const Object, allocator: std.mem.Allocator) !Object {
var out: Object = .{};
errdefer out.deinit(allocator);
for (self.attrs.items) |a| {
try out.set(allocator, a.type, a.value);
out.findPtr(a.type).?.sealed = a.sealed;
}
return out;
}
};
pub fn visible(obj: *const Object, logged_in: ?ck.CK_USER_TYPE) bool {
if (!obj.isPrivate()) return true;
return logged_in == ck.CKU_USER;
}
pub fn mapSetErr(e: anyerror) ck.CK_RV {
return switch (e) {
error.OutOfMemory => ck.CKR_HOST_MEMORY,
error.AttrTooLarge => ck.CKR_ATTRIBUTE_VALUE_INVALID,
error.TooManyAttributes => ck.CKR_TEMPLATE_INCONSISTENT,
else => ck.CKR_FUNCTION_FAILED,
};
}
const Entry = struct {
handle: ck.CK_OBJECT_HANDLE,
obj: Object,
};
pub const Store = struct {
slots: [config.max_objects]?Entry = @splat(null),
next_handle: ck.CK_OBJECT_HANDLE = 1,
pub fn insert(self: *Store, obj: Object) ?ck.CK_OBJECT_HANDLE {
for (&self.slots) |*slot| {
if (slot.* == null) {
const h = self.next_handle;
slot.* = .{ .handle = h, .obj = obj };
self.next_handle += 1;
return h;
}
}
return null;
}
pub fn getPtr(self: *Store, h: ck.CK_OBJECT_HANDLE) ?*Object {
if (h == ck.CK_INVALID_HANDLE) return null;
for (&self.slots) |*slot| {
if (slot.*) |*e| {
if (e.handle == h) return &e.obj;
}
}
return null;
}
pub fn destroy(self: *Store, allocator: std.mem.Allocator, h: ck.CK_OBJECT_HANDLE) bool {
if (h == ck.CK_INVALID_HANDLE) return false;
for (&self.slots) |*slot| {
if (slot.*) |*e| {
if (e.handle == h) {
e.obj.deinit(allocator);
slot.* = null;
return true;
}
}
}
return false;
}
pub fn count(self: *const Store) usize {
var n: usize = 0;
for (self.slots) |slot| {
if (slot != null) n += 1;
}
return n;
}
pub fn clear(self: *Store, allocator: std.mem.Allocator) void {
for (&self.slots) |*slot| {
if (slot.*) |*e| {
e.obj.deinit(allocator);
slot.* = null;
}
}
self.next_handle = 1;
}
pub fn deinit(self: *Store, allocator: std.mem.Allocator) void {
self.clear(allocator);
}
};
fn appendU32(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, v: u32) !void {
const x: u32 = v;
try buf.appendSlice(allocator, std.mem.asBytes(&x));
}
fn appendU64(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, v: u64) !void {
const x: u64 = v;
try buf.appendSlice(allocator, std.mem.asBytes(&x));
}
fn readU32(bytes: []const u8, cursor: *usize) !u32 {
const w = @sizeOf(u32);
if (cursor.* + w > bytes.len) return error.Truncated;
const v = std.mem.bytesToValue(u32, bytes[cursor.*..][0..w]);
cursor.* += w;
return v;
}
fn readU64(bytes: []const u8, cursor: *usize) !u64 {
const w = @sizeOf(u64);
if (cursor.* + w > bytes.len) return error.Truncated;
const v = std.mem.bytesToValue(u64, bytes[cursor.*..][0..w]);
cursor.* += w;
return v;
}
pub fn serialize(io: std.Io, allocator: std.mem.Allocator, store: *const Store, mk: ?keystore.MasterKey) ![]u8 {
var buf: std.ArrayList(u8) = .empty;
errdefer buf.deinit(allocator);
try appendU32(&buf, allocator, config.object_record_magic);
try appendU32(&buf, allocator, config.object_record_version);
var n: u32 = 0;
for (store.slots) |slot| {
if (slot) |e| {
if (e.obj.isToken()) n += 1;
}
}
try appendU32(&buf, allocator, n);
for (store.slots) |slot| {
if (slot) |e| {
if (!e.obj.isToken()) continue;
try appendU32(&buf, allocator, @intCast(e.obj.attrs.items.len));
for (e.obj.attrs.items) |a| {
try appendU64(&buf, allocator, @intCast(a.type));
if (!a.sealed and e.obj.shouldSeal(a.type)) {
const key = mk orelse return error.NoMasterKey;
const scratch = try allocator.alloc(u8, keystore.sealedLen(a.value.len));
defer allocator.free(scratch);
const wrote = try keystore.seal(io, &key, std.mem.asBytes(&a.type), a.value, scratch);
try appendU64(&buf, allocator, @intCast(wrote));
try buf.appendSlice(allocator, scratch[0..wrote]);
} else {
try appendU64(&buf, allocator, @intCast(a.value.len));
try buf.appendSlice(allocator, a.value);
}
}
}
}
return buf.toOwnedSlice(allocator);
}
fn parse(allocator: std.mem.Allocator, store: *Store, bytes: []const u8) !void {
var c: usize = 0;
const magic = try readU32(bytes, &c);
const version = try readU32(bytes, &c);
if (magic != config.object_record_magic or version != config.object_record_version) return error.BadHeader;
const obj_count = try readU32(bytes, &c);
var i: u32 = 0;
while (i < obj_count) : (i += 1) {
var obj: Object = .{};
var inserted = false;
defer if (!inserted) obj.deinit(allocator);
const attr_count = try readU32(bytes, &c);
if (attr_count > config.max_attributes_per_object) return error.TooManyAttributes;
var j: u32 = 0;
while (j < attr_count) : (j += 1) {
const t = try readU64(bytes, &c);
const len = try readU64(bytes, &c);
if (len > config.max_attr_value_len) return error.AttrTooLarge;
const n: usize = @intCast(len);
if (c + n > bytes.len) return error.Truncated;
try obj.set(allocator, @intCast(t), bytes[c .. c + n]);
c += n;
}
for (obj.attrs.items) |*a| {
if (obj.shouldSeal(a.type)) a.sealed = true;
}
if (store.insert(obj) == null) return error.StoreFull;
inserted = true;
}
}
pub fn save(io: std.Io, allocator: std.mem.Allocator, store: *const Store, mk: ?keystore.MasterKey) !void {
const data = try serialize(io, allocator, store, mk);
defer allocator.free(data);
var buf: [config.path_buf_len]u8 = undefined;
const path = try env.resolvePath(&buf, config.object_path_env, config.object_path_default);
var tmp_buf: [config.path_buf_len + 8]u8 = undefined;
const tmp = try std.fmt.bufPrint(&tmp_buf, "{s}.tmp", .{path});
const dir = std.Io.Dir.cwd();
try dir.writeFile(io, .{ .sub_path = tmp, .data = data });
try dir.rename(tmp, dir, path, io);
}
pub fn unlock(allocator: std.mem.Allocator, store: *Store, mk: keystore.MasterKey) !void {
for (&store.slots) |*slot| {
if (slot.*) |*e| {
if (!e.obj.isToken()) continue;
for (e.obj.attrs.items) |*a| {
if (!a.sealed) continue;
if (a.value.len < keystore.seal_overhead) return error.Corrupt;
const plain = try allocator.alloc(u8, a.value.len - keystore.seal_overhead);
_ = keystore.unseal(&mk, std.mem.asBytes(&a.type), a.value, plain) catch {
secureFree(allocator, plain);
return error.AuthFailed;
};
secureFree(allocator, a.value);
a.value = plain;
a.sealed = false;
}
}
}
}
pub fn lock(io: std.Io, allocator: std.mem.Allocator, store: *Store, mk: keystore.MasterKey) !void {
for (&store.slots) |*slot| {
if (slot.*) |*e| {
if (!e.obj.isToken()) continue;
for (e.obj.attrs.items) |*a| {
if (a.sealed or !e.obj.shouldSeal(a.type)) continue;
const sealed = try allocator.alloc(u8, keystore.sealedLen(a.value.len));
_ = keystore.seal(io, &mk, std.mem.asBytes(&a.type), a.value, sealed) catch {
secureFree(allocator, sealed);
return error.Seal;
};
secureFree(allocator, a.value);
a.value = sealed;
a.sealed = true;
}
}
}
}
pub fn scrubUnsealed(store: *Store) void {
for (&store.slots) |*slot| {
if (slot.*) |*e| {
if (!e.obj.isToken()) continue;
for (e.obj.attrs.items) |*a| {
if (a.sealed or !e.obj.shouldSeal(a.type)) continue;
std.crypto.secureZero(u8, a.value);
a.sealed = true;
}
}
}
}
pub fn load(io: std.Io, allocator: std.mem.Allocator, store: *Store) void {
var buf: [config.path_buf_len]u8 = undefined;
const path = env.resolvePath(&buf, config.object_path_env, config.object_path_default) catch return;
const bytes = std.Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(config.object_read_limit)) catch return;
defer allocator.free(bytes);
parse(allocator, store, bytes) catch store.clear(allocator);
}
test "set replaces an existing attribute and reports size" {
const a = std.testing.allocator;
var obj: Object = .{};
defer obj.deinit(a);
try obj.set(a, ck.CKA_LABEL, "first");
try obj.set(a, ck.CKA_LABEL, "second-value");
try std.testing.expectEqual(@as(usize, 1), obj.attrs.items.len);
try std.testing.expectEqualSlices(u8, "second-value", obj.get(ck.CKA_LABEL).?);
try std.testing.expectEqual(@as(ck.CK_ULONG, "second-value".len), obj.sizeBytes());
}
test "bool and class helpers read CK_BBOOL semantics" {
const a = std.testing.allocator;
var obj: Object = .{};
defer obj.deinit(a);
try std.testing.expect(!obj.isToken());
try obj.set(a, ck.CKA_TOKEN, &[_]u8{ck.CK_TRUE});
try obj.set(a, ck.CKA_PRIVATE, &[_]u8{ck.CK_FALSE});
try std.testing.expect(obj.isToken());
try std.testing.expect(!obj.isPrivate());
}
test "store hands out monotonic non-reused handles and destroys" {
const a = std.testing.allocator;
var store: Store = .{};
defer store.deinit(a);
var o1: Object = .{};
try o1.set(a, ck.CKA_CLASS, &[_]u8{0});
const h1 = store.insert(o1).?;
var o2: Object = .{};
try o2.set(a, ck.CKA_CLASS, &[_]u8{1});
const h2 = store.insert(o2).?;
try std.testing.expect(h1 != h2);
try std.testing.expect(store.getPtr(h1) != null);
try std.testing.expect(store.destroy(a, h1));
try std.testing.expect(store.getPtr(h1) == null);
try std.testing.expect(!store.destroy(a, h1));
var o3: Object = .{};
try o3.set(a, ck.CKA_CLASS, &[_]u8{2});
const h3 = store.insert(o3).?;
try std.testing.expect(h3 != h1 and h3 != h2);
try std.testing.expectEqual(@as(usize, 2), store.count());
}
test "serialize then parse round-trips only token objects" {
const a = std.testing.allocator;
var store: Store = .{};
defer store.deinit(a);
var tok: Object = .{};
try tok.set(a, ck.CKA_TOKEN, &[_]u8{ck.CK_TRUE});
try tok.set(a, ck.CKA_CLASS, &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0 });
try tok.set(a, ck.CKA_LABEL, "persisted");
_ = store.insert(tok);
var sess: Object = .{};
try sess.set(a, ck.CKA_TOKEN, &[_]u8{ck.CK_FALSE});
try sess.set(a, ck.CKA_LABEL, "ephemeral");
_ = store.insert(sess);
const data = try serialize(std.testing.io, a, &store, null);
defer a.free(data);
var restored: Store = .{};
defer restored.deinit(a);
try parse(a, &restored, data);
try std.testing.expectEqual(@as(usize, 1), restored.count());
var found_label: ?[]const u8 = null;
for (&restored.slots) |*slot| {
if (slot.*) |*e| found_label = e.obj.get(ck.CKA_LABEL);
}
try std.testing.expectEqualSlices(u8, "persisted", found_label.?);
}
test "serialize seals a sensitive value at rest and unlock recovers it" {
const a = std.testing.allocator;
const io = std.testing.io;
const mk: keystore.MasterKey = @splat(0x5a);
const wrong: keystore.MasterKey = @splat(0x17);
const secret = "SUPER-SECRET-KEY-MATERIAL";
var store: Store = .{};
defer store.deinit(a);
var key: Object = .{};
try key.set(a, ck.CKA_TOKEN, &[_]u8{ck.CK_TRUE});
try key.set(a, ck.CKA_CLASS, &[_]u8{ 4, 0, 0, 0, 0, 0, 0, 0 });
try key.set(a, ck.CKA_SENSITIVE, &[_]u8{ck.CK_TRUE});
try key.set(a, ck.CKA_VALUE, secret);
_ = store.insert(key);
const data = try serialize(io, a, &store, mk);
defer a.free(data);
try std.testing.expect(std.mem.indexOf(u8, data, secret) == null);
var restored: Store = .{};
defer restored.deinit(a);
try parse(a, &restored, data);
var sealed_value: ?[]const u8 = null;
for (&restored.slots) |*slot| {
if (slot.*) |*e| {
if (e.obj.findPtr(ck.CKA_VALUE)) |attr| {
try std.testing.expect(attr.sealed);
sealed_value = attr.value;
}
}
}
try std.testing.expect(sealed_value != null);
try std.testing.expect(std.mem.indexOf(u8, sealed_value.?, secret) == null);
try std.testing.expectError(error.AuthFailed, unlock(a, &restored, wrong));
try unlock(a, &restored, mk);
for (&restored.slots) |*slot| {
if (slot.*) |*e| {
const v = e.obj.get(ck.CKA_VALUE).?;
try std.testing.expectEqualSlices(u8, secret, v);
try std.testing.expect(!e.obj.findPtr(ck.CKA_VALUE).?.sealed);
}
}
}
test "secureFree clears the secret from the value buffer" {
var backing: [64]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&backing);
const a = fba.allocator();
const v = try a.dupe(u8, "SUPER-SECRET-KEY");
const region = backing[0..v.len];
try std.testing.expect(std.mem.indexOf(u8, region, "SECRET") != null);
secureFree(a, v);
try std.testing.expect(std.mem.indexOf(u8, region, "SECRET") == null);
}
test "deinit clears a plaintext secret attribute from its buffer" {
var backing: [512]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&backing);
const a = fba.allocator();
var obj: Object = .{};
try obj.set(a, ck.CKA_VALUE, "AES-256-SECRET-KEY-BYTES");
const stored = obj.findPtr(ck.CKA_VALUE).?.value;
const region = stored[0..stored.len];
try std.testing.expect(std.mem.indexOf(u8, region, "SECRET") != null);
obj.deinit(a);
try std.testing.expect(std.mem.indexOf(u8, region, "SECRET") == null);
}
test "parse rejects a bad magic without leaking" {
const a = std.testing.allocator;
var store: Store = .{};
defer store.deinit(a);
const junk = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
try std.testing.expectError(error.BadHeader, parse(a, &store, &junk));
try std.testing.expectEqual(@as(usize, 0), store.count());
}
test "parse fails safe on a truncated record and clear frees partial inserts" {
const a = std.testing.allocator;
var store: Store = .{};
defer store.deinit(a);
var good: Store = .{};
defer good.deinit(a);
var tok: Object = .{};
try tok.set(a, ck.CKA_TOKEN, &[_]u8{ck.CK_TRUE});
try tok.set(a, ck.CKA_LABEL, "x");
_ = good.insert(tok);
const data = try serialize(std.testing.io, a, &good, null);
defer a.free(data);
try std.testing.expectError(error.Truncated, parse(a, &store, data[0 .. data.len - 1]));
store.clear(a);
try std.testing.expectEqual(@as(usize, 0), store.count());
}
test "scrubUnsealed fail-closes plaintext secrets after a failed re-seal" {
const a = std.testing.allocator;
const io = std.testing.io;
const secret = "PLAINTEXT-KEY-MUST-NOT-SURVIVE-LOGOUT";
var store: Store = .{};
defer store.deinit(a);
var key: Object = .{};
try key.set(a, ck.CKA_TOKEN, &[_]u8{ck.CK_TRUE});
try key.set(a, ck.CKA_SENSITIVE, &[_]u8{ck.CK_TRUE});
try key.set(a, ck.CKA_VALUE, secret);
_ = store.insert(key);
const mk: keystore.MasterKey = @splat(0x42);
var failing = std.testing.FailingAllocator.init(a, .{ .fail_index = 0 });
try std.testing.expectError(error.OutOfMemory, lock(io, failing.allocator(), &store, mk));
const attr = store.slots[0].?.obj.findPtr(ck.CKA_VALUE).?;
try std.testing.expect(!attr.sealed);
try std.testing.expect(std.mem.indexOf(u8, attr.value, "SURVIVE") != null);
scrubUnsealed(&store);
try std.testing.expect(attr.sealed);
try std.testing.expect(std.mem.indexOf(u8, attr.value, "SURVIVE") == null);
for (attr.value) |b| try std.testing.expectEqual(@as(u8, 0), b);
}

View File

@ -0,0 +1,281 @@
// ©AngelaMos | 2026
// session.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const digest = @import("../crypto/digest.zig");
const mac = @import("../crypto/mac.zig");
const cipher = @import("../crypto/cipher.zig");
const ecdsa = @import("../crypto/ecdsa.zig");
const rsa = @import("../crypto/rsa.zig");
pub const Find = struct {
matches: [config.max_objects]ck.CK_OBJECT_HANDLE = undefined,
count: usize = 0,
cursor: usize = 0,
active: bool = false,
};
pub const RsaSig = struct {
key: ck.CK_OBJECT_HANDLE,
params: rsa.SignParams,
sig_len: usize,
};
pub const RsaCrypt = struct {
key: ck.CK_OBJECT_HANDLE,
params: rsa.CryptParams,
out_len: usize,
};
pub const SignOp = union(enum) {
mac: mac.Mac,
ec: ecdsa.SignState,
rsa: RsaSig,
pub fn update(self: *SignOp, data: []const u8) void {
switch (self.*) {
.mac => |*m| m.update(data),
.ec => |*e| e.update(data),
.rsa => {},
}
}
pub fn zeroize(self: *SignOp) void {
std.crypto.secureZero(u8, std.mem.asBytes(self));
}
};
pub const VerifyOp = union(enum) {
mac: mac.Mac,
ec: ecdsa.VerifyState,
rsa: RsaSig,
pub fn update(self: *VerifyOp, data: []const u8) void {
switch (self.*) {
.mac => |*m| m.update(data),
.ec => |*e| e.update(data),
.rsa => {},
}
}
pub fn zeroize(self: *VerifyOp) void {
std.crypto.secureZero(u8, std.mem.asBytes(self));
}
};
pub const EncryptOp = union(enum) {
aes: cipher.Cipher,
rsa: RsaCrypt,
pub fn zeroize(self: *EncryptOp) void {
std.crypto.secureZero(u8, std.mem.asBytes(self));
}
};
pub const DecryptOp = union(enum) {
aes: cipher.Cipher,
rsa: RsaCrypt,
pub fn zeroize(self: *DecryptOp) void {
std.crypto.secureZero(u8, std.mem.asBytes(self));
}
};
pub const Session = struct {
slot: ck.CK_SLOT_ID,
flags: ck.CK_FLAGS,
find: Find = .{},
digest_op: ?digest.Hasher = null,
sign_op: ?SignOp = null,
verify_op: ?VerifyOp = null,
encrypt_op: ?EncryptOp = null,
decrypt_op: ?DecryptOp = null,
pub fn endDigest(self: *Session) void {
if (self.digest_op) |*o| std.crypto.secureZero(u8, std.mem.asBytes(o));
self.digest_op = null;
}
pub fn endSign(self: *Session) void {
if (self.sign_op) |*o| o.zeroize();
self.sign_op = null;
}
pub fn endVerify(self: *Session) void {
if (self.verify_op) |*o| o.zeroize();
self.verify_op = null;
}
pub fn endEncrypt(self: *Session) void {
if (self.encrypt_op) |*o| o.zeroize();
self.encrypt_op = null;
}
pub fn endDecrypt(self: *Session) void {
if (self.decrypt_op) |*o| o.zeroize();
self.decrypt_op = null;
}
};
pub const Table = struct {
slots: [config.max_sessions]?Session = @splat(null),
pub fn open(self: *Table, slot: ck.CK_SLOT_ID, flags: ck.CK_FLAGS) ?ck.CK_SESSION_HANDLE {
for (&self.slots, 0..) |*s, i| {
if (s.* == null) {
std.crypto.secureZero(u8, std.mem.asBytes(s));
s.* = .{ .slot = slot, .flags = flags };
return @intCast(i + 1);
}
}
return null;
}
pub fn get(self: *Table, h: ck.CK_SESSION_HANDLE) ?*Session {
if (h == 0 or h > config.max_sessions) return null;
if (self.slots[h - 1]) |*s| return s;
return null;
}
pub fn close(self: *Table, h: ck.CK_SESSION_HANDLE) bool {
if (h == 0 or h > config.max_sessions) return false;
if (self.slots[h - 1] == null) return false;
std.crypto.secureZero(u8, std.mem.asBytes(&self.slots[h - 1]));
self.slots[h - 1] = null;
return true;
}
pub fn closeAll(self: *Table, slot: ck.CK_SLOT_ID) void {
for (&self.slots) |*s| {
if (s.*) |*sp| {
if (sp.slot == slot) {
std.crypto.secureZero(u8, std.mem.asBytes(s));
s.* = null;
}
}
}
}
pub fn wipeAll(self: *Table) void {
std.crypto.secureZero(u8, std.mem.asBytes(&self.slots));
}
pub fn anyOpen(self: *Table) bool {
for (&self.slots) |*s| {
if (s.* != null) return true;
}
return false;
}
pub fn count(self: *Table) ck.CK_ULONG {
var n: ck.CK_ULONG = 0;
for (&self.slots) |*s| {
if (s.* != null) n += 1;
}
return n;
}
pub fn countRw(self: *Table) ck.CK_ULONG {
var n: ck.CK_ULONG = 0;
for (&self.slots) |*s| {
if (s.*) |*sp| {
if ((sp.flags & ck.CKF_RW_SESSION) != 0) n += 1;
}
}
return n;
}
};
test "open returns nonzero handles and get resolves them" {
var t: Table = .{};
const h1 = t.open(0, ck.CKF_SERIAL_SESSION).?;
const h2 = t.open(0, ck.CKF_SERIAL_SESSION | ck.CKF_RW_SESSION).?;
try std.testing.expect(h1 != 0 and h2 != 0 and h1 != h2);
try std.testing.expectEqual(@as(ck.CK_ULONG, 2), t.count());
try std.testing.expectEqual(@as(ck.CK_ULONG, 1), t.countRw());
try std.testing.expect(t.get(h1) != null);
try std.testing.expect(t.get(9999) == null);
}
test "close frees the slot and closeAll empties the table" {
var t: Table = .{};
const h = t.open(0, ck.CKF_SERIAL_SESSION).?;
try std.testing.expect(t.close(h));
try std.testing.expect(!t.close(h));
try std.testing.expect(!t.anyOpen());
_ = t.open(0, ck.CKF_SERIAL_SESSION);
t.closeAll(0);
try std.testing.expect(!t.anyOpen());
}
fn expectAllZero(bytes: []const u8) !void {
for (bytes) |b| try std.testing.expectEqual(@as(u8, 0), b);
}
test "EncryptOp.zeroize zeros the AES key material" {
var op: EncryptOp = .{ .aes = .{ .mode = .cbc, .encrypt = true, .key_len = 32 } };
const key: []u8 = &op.aes.key_buf;
@memset(key, 0xAA);
op.zeroize();
try expectAllZero(key);
}
test "DecryptOp.zeroize zeros the AES key material" {
var op: DecryptOp = .{ .aes = .{ .mode = .cbc, .encrypt = false, .key_len = 16 } };
const key: []u8 = &op.aes.key_buf;
@memset(key, 0xAA);
op.zeroize();
try expectAllZero(key);
}
test "SignOp.zeroize zeros the EC private scalar" {
const scalar = [_]u8{0xAB} ** 32;
var op: SignOp = .{ .ec = ecdsa.SignState.init(.p256, ck.CKM_ECDSA, &scalar).? };
const sc: []u8 = &op.ec.scalar;
op.zeroize();
try expectAllZero(sc);
}
test "VerifyOp.zeroize zeros HMAC key state" {
var op: VerifyOp = .{ .mac = undefined };
const st: []u8 = std.mem.asBytes(&op.mac);
@memset(st, 0xCD);
op.zeroize();
try expectAllZero(st);
}
test "endEncrypt clears the op and removes the secret from the slot" {
var t: Table = .{};
const h = t.open(0, ck.CKF_SERIAL_SESSION).?;
const sess = t.get(h).?;
sess.encrypt_op = .{ .aes = .{ .mode = .gcm, .encrypt = true, .key_len = 32 } };
const key: []u8 = &sess.encrypt_op.?.aes.key_buf;
@memset(key, 0x5C);
sess.endEncrypt();
try std.testing.expect(sess.encrypt_op == null);
try std.testing.expect(std.mem.indexOfScalar(u8, key, 0x5C) == null);
}
test "close removes an active op's secret from the slot" {
var t: Table = .{};
const h = t.open(0, ck.CKF_SERIAL_SESSION).?;
const sess = t.get(h).?;
sess.decrypt_op = .{ .aes = .{ .mode = .cbc, .encrypt = false, .key_len = 32 } };
const key: []u8 = &sess.decrypt_op.?.aes.key_buf;
@memset(key, 0x5C);
try std.testing.expect(t.close(h));
try std.testing.expect(std.mem.indexOfScalar(u8, key, 0x5C) == null);
}
test "wipeAll zeros secret material in every slot" {
var t: Table = .{};
const h = t.open(0, ck.CKF_SERIAL_SESSION).?;
const sess = t.get(h).?;
sess.sign_op = .{ .mac = undefined };
const st: []u8 = std.mem.asBytes(&sess.sign_op.?.mac);
@memset(st, 0xEF);
t.wipeAll();
try expectAllZero(st);
}

View File

@ -0,0 +1,140 @@
// ©AngelaMos | 2026
// state.zig
const std = @import("std");
const builtin = @import("builtin");
const ck = @import("../ck.zig");
const lock = @import("lock.zig");
const token = @import("token.zig");
const session = @import("session.zig");
const object_store = @import("object_store.zig");
const keystore = @import("../crypto/keystore.zig");
pub const Locking = enum { none, os };
pub const Instance = struct {
debug_alloc: std.heap.DebugAllocator(.{}) = undefined,
threaded: std.Io.Threaded = undefined,
locking: Locking = .none,
token: token.Token = .{},
sessions: session.Table = .{},
objects: object_store.Store = .{},
logged_in: ?ck.CK_USER_TYPE = null,
mk: ?keystore.MasterKey = null,
pub fn allocator(self: *Instance) std.mem.Allocator {
return if (builtin.mode == .Debug) self.debug_alloc.allocator() else std.heap.smp_allocator;
}
pub fn io(self: *Instance) std.Io {
return self.threaded.io();
}
pub fn wipeMasterKey(self: *Instance) void {
if (self.mk) |*mk| std.crypto.secureZero(u8, mk);
self.mk = null;
}
pub fn relock(self: *Instance) void {
if (self.mk) |*mk| object_store.lock(self.io(), self.allocator(), &self.objects, mk.*) catch {
object_store.scrubUnsealed(&self.objects);
};
self.wipeMasterKey();
}
};
pub var mutex: lock.Lock = .{};
var storage: Instance = undefined;
var present: bool = false;
var inflight: usize = 0;
var generation: u64 = 0;
pub fn current() ?*Instance {
return if (@atomicLoad(bool, &present, .acquire)) &storage else null;
}
pub fn isInitialized() bool {
return @atomicLoad(bool, &present, .acquire);
}
pub fn initialize(locking: Locking) void {
storage = .{
.debug_alloc = .init,
.threaded = .init(std.heap.smp_allocator, .{}),
.locking = locking,
};
storage.token = token.load(storage.io(), storage.allocator());
object_store.load(storage.io(), storage.allocator(), &storage.objects);
@atomicStore(bool, &present, true, .release);
}
pub fn finalize() ck.CK_RV {
mutex.lock();
if (!@atomicLoad(bool, &present, .acquire)) {
mutex.unlock();
return ck.CKR_CRYPTOKI_NOT_INITIALIZED;
}
@atomicStore(bool, &present, false, .release);
mutex.unlock();
while (true) {
mutex.lock();
const pending = inflight;
mutex.unlock();
if (pending == 0) break;
std.Thread.yield() catch std.atomic.spinLoopHint();
}
mutex.lock();
defer mutex.unlock();
storage.sessions.wipeAll();
storage.wipeMasterKey();
storage.objects.deinit(storage.allocator());
storage.threaded.deinit();
_ = storage.debug_alloc.deinit();
return ck.CKR_OK;
}
pub fn cryptoBegin() u64 {
inflight += 1;
return generation;
}
pub fn cryptoEnd() void {
inflight -= 1;
}
pub fn cryptoAbort() void {
mutex.lock();
inflight -= 1;
mutex.unlock();
}
pub fn bumpGeneration() void {
generation += 1;
}
pub fn currentGeneration() u64 {
return generation;
}
pub const InitOutcome = union(enum) {
ok: Locking,
err: ck.CK_RV,
};
pub fn parseInitArgs(p: ?*anyopaque) InitOutcome {
if (p == null) return .{ .ok = .none };
const args: *ck.CK_C_INITIALIZE_ARGS = @ptrCast(@alignCast(p.?));
if (args.pReserved != null) return .{ .err = ck.CKR_ARGUMENTS_BAD };
var cbs: u8 = 0;
if (args.CreateMutex != null) cbs += 1;
if (args.DestroyMutex != null) cbs += 1;
if (args.LockMutex != null) cbs += 1;
if (args.UnlockMutex != null) cbs += 1;
if (cbs != 0 and cbs != 4) return .{ .err = ck.CKR_ARGUMENTS_BAD };
const os_locking_ok = (args.flags & ck.CKF_OS_LOCKING_OK) != 0;
if (os_locking_ok) return .{ .ok = .os };
if (cbs == 4) return .{ .err = ck.CKR_CANT_LOCK };
return .{ .ok = .none };
}

View File

@ -0,0 +1,168 @@
// ©AngelaMos | 2026
// token.zig
const std = @import("std");
const config = @import("../config.zig");
const pin = @import("../crypto/pin.zig");
const keystore = @import("../crypto/keystore.zig");
const env = @import("env.zig");
pub const PinSlot = struct {
salt: pin.Salt,
hash: pin.Hash,
};
pub const Token = struct {
initialized: bool = false,
label: [config.label_len]u8 = @splat(' '),
so: PinSlot = std.mem.zeroes(PinSlot),
user: ?PinSlot = null,
so_fail: u32 = 0,
user_fail: u32 = 0,
user_mk: ?keystore.Wrapped = null,
};
const flag_initialized: u32 = 1 << 0;
const flag_user_present: u32 = 1 << 1;
const flag_user_mk: u32 = 1 << 2;
const Record = extern struct {
magic: u32,
version: u32,
flags: u32,
label: [config.label_len]u8,
so_salt: [pin.salt_len]u8,
so_hash: [pin.hash_len]u8,
user_salt: [pin.salt_len]u8,
user_hash: [pin.hash_len]u8,
so_fail: u32,
user_fail: u32,
mk_salt: [pin.salt_len]u8,
mk_nonce: [keystore.nonce_len]u8,
mk_ct: [keystore.mk_len]u8,
mk_tag: [keystore.tag_len]u8,
};
fn serialize(t: Token) Record {
var r = std.mem.zeroes(Record);
r.magic = config.token_record_magic;
r.version = config.token_record_version;
r.flags = (if (t.initialized) flag_initialized else 0) | (if (t.user != null) flag_user_present else 0) | (if (t.user_mk != null) flag_user_mk else 0);
r.label = t.label;
r.so_salt = t.so.salt;
r.so_hash = t.so.hash;
if (t.user) |u| {
r.user_salt = u.salt;
r.user_hash = u.hash;
}
if (t.user_mk) |w| {
r.mk_salt = w.salt;
r.mk_nonce = w.nonce;
r.mk_ct = w.ct;
r.mk_tag = w.tag;
}
r.so_fail = t.so_fail;
r.user_fail = t.user_fail;
return r;
}
fn deserialize(r: *const Record) Token {
return .{
.initialized = (r.flags & flag_initialized) != 0,
.label = r.label,
.so = .{ .salt = r.so_salt, .hash = r.so_hash },
.user = if ((r.flags & flag_user_present) != 0) PinSlot{ .salt = r.user_salt, .hash = r.user_hash } else null,
.so_fail = r.so_fail,
.user_fail = r.user_fail,
.user_mk = if ((r.flags & flag_user_mk) != 0) keystore.Wrapped{ .salt = r.mk_salt, .nonce = r.mk_nonce, .ct = r.mk_ct, .tag = r.mk_tag } else null,
};
}
pub fn resolvePath(buf: []u8) ![]const u8 {
return env.resolvePath(buf, config.token_path_env, config.token_path_default);
}
pub fn saveTo(io: std.Io, path: []const u8, t: Token) !void {
const rec = serialize(t);
var tmp_buf: [config.path_buf_len + 8]u8 = undefined;
const tmp = try std.fmt.bufPrint(&tmp_buf, "{s}.tmp", .{path});
const dir = std.Io.Dir.cwd();
try dir.writeFile(io, .{ .sub_path = tmp, .data = std.mem.asBytes(&rec) });
try dir.rename(tmp, dir, path, io);
}
pub fn loadFrom(io: std.Io, allocator: std.mem.Allocator, path: []const u8) Token {
const bytes = std.Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(config.token_read_limit)) catch return .{};
defer allocator.free(bytes);
if (bytes.len != @sizeOf(Record)) return .{};
var rec: Record = undefined;
@memcpy(std.mem.asBytes(&rec), bytes[0..@sizeOf(Record)]);
if (rec.magic != config.token_record_magic or rec.version != config.token_record_version) return .{};
return deserialize(&rec);
}
pub fn save(io: std.Io, t: Token) !void {
var buf: [config.path_buf_len]u8 = undefined;
const path = try resolvePath(&buf);
try saveTo(io, path, t);
}
pub fn load(io: std.Io, allocator: std.mem.Allocator) Token {
var buf: [config.path_buf_len]u8 = undefined;
const path = resolvePath(&buf) catch return .{};
return loadFrom(io, allocator, path);
}
test "serialize then deserialize round-trips an initialized token with a user PIN" {
var t: Token = .{ .initialized = true, .so_fail = 2, .user_fail = 1 };
t.label = @splat('X');
t.so = .{ .salt = @splat(3), .hash = @splat(4) };
t.user = .{ .salt = @splat(5), .hash = @splat(6) };
const rec = serialize(t);
const back = deserialize(&rec);
try std.testing.expect(back.initialized);
try std.testing.expectEqualSlices(u8, &t.label, &back.label);
try std.testing.expectEqual(t.so.salt, back.so.salt);
try std.testing.expectEqual(t.so.hash, back.so.hash);
try std.testing.expect(back.user != null);
try std.testing.expectEqual(t.user.?.hash, back.user.?.hash);
try std.testing.expectEqual(@as(u32, 2), back.so_fail);
try std.testing.expectEqual(@as(u32, 1), back.user_fail);
}
test "an uninitialized token has no user PIN after a round-trip" {
const rec = serialize(.{});
const back = deserialize(&rec);
try std.testing.expect(!back.initialized);
try std.testing.expect(back.user == null);
}
test "saveTo then loadFrom persists across a file" {
var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
defer threaded.deinit();
const io = threaded.io();
const path = "/tmp/angelamos-hsm-unit-token.bin";
std.Io.Dir.cwd().deleteFile(io, path) catch {};
defer std.Io.Dir.cwd().deleteFile(io, path) catch {};
var t: Token = .{ .initialized = true };
t.label = @splat('Z');
t.so = .{ .salt = @splat(9), .hash = @splat(8) };
try saveTo(io, path, t);
const back = loadFrom(io, std.testing.allocator, path);
try std.testing.expect(back.initialized);
try std.testing.expectEqual(t.so.hash, back.so.hash);
try std.testing.expect(back.user == null);
}
test "loadFrom a missing file yields a default uninitialized token" {
var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
defer threaded.deinit();
const io = threaded.io();
const back = loadFrom(io, std.testing.allocator, "/tmp/angelamos-hsm-does-not-exist.bin");
try std.testing.expect(!back.initialized);
}

View File

@ -0,0 +1,302 @@
// ©AngelaMos | 2026
// cipher.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const aes = std.crypto.core.aes;
const aesgcm = std.crypto.aead.aes_gcm;
const block = config.aes_block_len;
pub const Mode = enum { cbc, cbc_pad, gcm };
pub const Error = error{
KeySize,
DataLenRange,
EncryptedDataLenRange,
EncryptedDataInvalid,
AadTooLarge,
IvInvalid,
};
pub fn modeOf(mech: ck.CK_MECHANISM_TYPE) ?Mode {
return switch (mech) {
ck.CKM_AES_CBC => .cbc,
ck.CKM_AES_CBC_PAD => .cbc_pad,
ck.CKM_AES_GCM => .gcm,
else => null,
};
}
pub fn validKeyLen(len: usize) bool {
return len == config.aes_min_key_bytes or len == config.aes_max_key_bytes;
}
fn encBlockRaw(key: []const u8, in: *const [block]u8, out: *[block]u8) void {
switch (key.len) {
16 => aes.Aes128.initEnc(key[0..16].*).encrypt(out, in),
32 => aes.Aes256.initEnc(key[0..32].*).encrypt(out, in),
else => unreachable,
}
}
fn decBlockRaw(key: []const u8, in: *const [block]u8, out: *[block]u8) void {
switch (key.len) {
16 => aes.Aes128.initDec(key[0..16].*).decrypt(out, in),
32 => aes.Aes256.initDec(key[0..32].*).decrypt(out, in),
else => unreachable,
}
}
pub const Cipher = struct {
mode: Mode,
encrypt: bool,
key_buf: [32]u8 = @splat(0),
key_len: u8 = 0,
chain: [block]u8 = @splat(0),
partial: [block]u8 = @splat(0),
partial_len: u8 = 0,
held: [block]u8 = @splat(0),
has_held: bool = false,
iv: [config.gcm_iv_len]u8 = @splat(0),
aad_buf: [config.max_gcm_aad_len]u8 = @splat(0),
aad_len: usize = 0,
fn key(self: *const Cipher) []const u8 {
return self.key_buf[0..self.key_len];
}
fn cbcEncStep(self: *Cipher, in16: *const [block]u8, out16: *[block]u8) void {
var x: [block]u8 = undefined;
for (0..block) |j| x[j] = in16[j] ^ self.chain[j];
encBlockRaw(self.key(), &x, out16);
self.chain = out16.*;
}
fn cbcDecStep(self: *Cipher, in16: *const [block]u8, out16: *[block]u8) void {
var d: [block]u8 = undefined;
decBlockRaw(self.key(), in16, &d);
for (0..block) |j| out16[j] = d[j] ^ self.chain[j];
self.chain = in16.*;
}
pub fn encryptUpdate(self: *Cipher, input: []const u8, out: []u8) usize {
var o: usize = 0;
var i: usize = 0;
while (i < input.len) {
const take = @min(block - self.partial_len, input.len - i);
@memcpy(self.partial[self.partial_len..][0..take], input[i .. i + take]);
self.partial_len += @intCast(take);
i += take;
if (self.partial_len == block) {
self.cbcEncStep(&self.partial, out[o..][0..block]);
o += block;
self.partial_len = 0;
}
}
return o;
}
pub fn encryptFinal(self: *Cipher, out: []u8) Error!usize {
if (self.mode == .cbc) {
if (self.partial_len != 0) return Error.DataLenRange;
return 0;
}
const padlen: u8 = @intCast(block - self.partial_len);
for (self.partial_len..block) |j| self.partial[j] = padlen;
self.cbcEncStep(&self.partial, out[0..block]);
self.partial_len = 0;
return block;
}
pub fn decryptUpdate(self: *Cipher, input: []const u8, out: []u8) usize {
var o: usize = 0;
var i: usize = 0;
while (i < input.len) {
const take = @min(block - self.partial_len, input.len - i);
@memcpy(self.partial[self.partial_len..][0..take], input[i .. i + take]);
self.partial_len += @intCast(take);
i += take;
if (self.partial_len == block) {
if (self.mode == .cbc_pad) {
if (self.has_held) {
self.cbcDecStep(&self.held, out[o..][0..block]);
o += block;
}
self.held = self.partial;
self.has_held = true;
} else {
self.cbcDecStep(&self.partial, out[o..][0..block]);
o += block;
}
self.partial_len = 0;
}
}
return o;
}
pub fn decryptFinal(self: *Cipher, out: []u8) Error!usize {
if (self.partial_len != 0) return Error.EncryptedDataLenRange;
if (self.mode == .cbc) return 0;
if (!self.has_held) return Error.EncryptedDataLenRange;
var pt: [block]u8 = undefined;
self.cbcDecStep(&self.held, &pt);
self.has_held = false;
const padlen = pt[block - 1];
if (padlen == 0 or padlen > block) return Error.EncryptedDataInvalid;
var bad: u8 = 0;
for (0..block) |j| {
const is_pad = j >= block - padlen;
if (is_pad) bad |= pt[j] ^ padlen;
}
if (bad != 0) return Error.EncryptedDataInvalid;
const keep = block - padlen;
@memcpy(out[0..keep], pt[0..keep]);
return keep;
}
pub fn gcmEncrypt(self: *Cipher, input: []const u8, out: []u8) usize {
std.debug.assert(out.len >= input.len + config.gcm_tag_len);
const tag: *[config.gcm_tag_len]u8 = out[input.len..][0..config.gcm_tag_len];
const ad = self.aad_buf[0..self.aad_len];
switch (self.key_len) {
16 => aesgcm.Aes128Gcm.encrypt(out[0..input.len], tag, input, ad, self.iv, self.key_buf[0..16].*),
32 => aesgcm.Aes256Gcm.encrypt(out[0..input.len], tag, input, ad, self.iv, self.key_buf[0..32].*),
else => unreachable,
}
return input.len + config.gcm_tag_len;
}
pub fn gcmDecrypt(self: *Cipher, input: []const u8, out: []u8) Error!usize {
if (input.len < config.gcm_tag_len) return Error.EncryptedDataLenRange;
const ct_len = input.len - config.gcm_tag_len;
std.debug.assert(out.len >= ct_len);
const tag: [config.gcm_tag_len]u8 = input[ct_len..][0..config.gcm_tag_len].*;
const ad = self.aad_buf[0..self.aad_len];
switch (self.key_len) {
16 => aesgcm.Aes128Gcm.decrypt(out[0..ct_len], input[0..ct_len], tag, ad, self.iv, self.key_buf[0..16].*) catch return Error.EncryptedDataInvalid,
32 => aesgcm.Aes256Gcm.decrypt(out[0..ct_len], input[0..ct_len], tag, ad, self.iv, self.key_buf[0..32].*) catch return Error.EncryptedDataInvalid,
else => unreachable,
}
return ct_len;
}
};
pub fn encryptOutLen(mode: Mode, in_len: usize) usize {
return switch (mode) {
.cbc => in_len,
.cbc_pad => (in_len / block + 1) * block,
.gcm => in_len + config.gcm_tag_len,
};
}
pub fn decryptOutLen(mode: Mode, in_len: usize) usize {
return switch (mode) {
.cbc, .cbc_pad => in_len,
.gcm => if (in_len >= config.gcm_tag_len) in_len - config.gcm_tag_len else 0,
};
}
fn testKey() [32]u8 {
var k: [32]u8 = undefined;
for (0..32) |j| k[j] = @intCast(j);
return k;
}
test "AES-256-CBC single-block matches a NIST SP800-38A vector" {
const key = [_]u8{
0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81,
0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4,
};
const iv = [_]u8{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
const pt = [_]u8{ 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a };
const expect = [_]u8{ 0xf5, 0x8c, 0x4c, 0x04, 0xd6, 0xe5, 0xf1, 0xba, 0x77, 0x9e, 0xab, 0xfb, 0x5f, 0x7b, 0xfb, 0xd6 };
var c: Cipher = .{ .mode = .cbc, .encrypt = true, .key_len = 32 };
c.key_buf = key;
c.chain = iv;
var out: [16]u8 = undefined;
const n = c.encryptUpdate(&pt, &out);
try std.testing.expectEqual(@as(usize, 16), n);
try std.testing.expectEqualSlices(u8, &expect, &out);
}
test "CBC-PAD round-trips arbitrary lengths" {
const key = testKey();
const iv = [_]u8{1} ** 16;
for ([_]usize{ 0, 1, 15, 16, 17, 100 }) |len| {
var pt: [100]u8 = undefined;
for (0..len) |j| pt[j] = @intCast((j * 7) & 0xff);
var enc: Cipher = .{ .mode = .cbc_pad, .encrypt = true, .key_len = 32 };
enc.key_buf = key;
enc.chain = iv;
var ct: [128]u8 = undefined;
var cn = enc.encryptUpdate(pt[0..len], &ct);
cn += try enc.encryptFinal(ct[cn..]);
try std.testing.expectEqual(encryptOutLen(.cbc_pad, len), cn);
var dec: Cipher = .{ .mode = .cbc_pad, .encrypt = false, .key_len = 32 };
dec.key_buf = key;
dec.chain = iv;
var back: [128]u8 = undefined;
var bn = dec.decryptUpdate(ct[0..cn], &back);
bn += try dec.decryptFinal(back[bn..]);
try std.testing.expectEqual(len, bn);
try std.testing.expectEqualSlices(u8, pt[0..len], back[0..bn]);
}
}
test "CBC-PAD streaming in small chunks equals one-shot" {
const key = testKey();
const iv = [_]u8{2} ** 16;
var pt: [70]u8 = undefined;
for (0..70) |j| pt[j] = @intCast(j);
var enc: Cipher = .{ .mode = .cbc_pad, .encrypt = true, .key_len = 16 };
enc.key_buf = key;
enc.chain = iv;
var ct: [96]u8 = undefined;
var cn = enc.encryptUpdate(&pt, &ct);
cn += try enc.encryptFinal(ct[cn..]);
var dec: Cipher = .{ .mode = .cbc_pad, .encrypt = false, .key_len = 16 };
dec.key_buf = key;
dec.chain = iv;
var back: [96]u8 = undefined;
var bn: usize = 0;
var i: usize = 0;
while (i < cn) : (i += 7) {
const end = @min(i + 7, cn);
bn += dec.decryptUpdate(ct[i..end], back[bn..]);
}
bn += try dec.decryptFinal(back[bn..]);
try std.testing.expectEqual(@as(usize, 70), bn);
try std.testing.expectEqualSlices(u8, &pt, back[0..bn]);
}
test "GCM round-trips and rejects a tampered tag" {
const key = testKey();
var c: Cipher = .{ .mode = .gcm, .encrypt = true, .key_len = 32 };
c.key_buf = key;
c.iv = [_]u8{7} ** 12;
const pt = "authenticated secret";
var ct: [64]u8 = undefined;
const cn = c.gcmEncrypt(pt, &ct);
try std.testing.expectEqual(pt.len + 16, cn);
var d: Cipher = .{ .mode = .gcm, .encrypt = false, .key_len = 32 };
d.key_buf = key;
d.iv = [_]u8{7} ** 12;
var back: [64]u8 = undefined;
const bn = try d.gcmDecrypt(ct[0..cn], &back);
try std.testing.expectEqualSlices(u8, pt, back[0..bn]);
ct[0] ^= 0x01;
var d2: Cipher = .{ .mode = .gcm, .encrypt = false, .key_len = 32 };
d2.key_buf = key;
d2.iv = [_]u8{7} ** 12;
try std.testing.expectError(Error.EncryptedDataInvalid, d2.gcmDecrypt(ct[0..cn], &back));
}

View File

@ -0,0 +1,89 @@
// ©AngelaMos | 2026
// digest.zig
const std = @import("std");
const ck = @import("../ck.zig");
const sha2 = std.crypto.hash.sha2;
pub const max_digest_len = sha2.Sha512.digest_length;
pub const Hasher = union(enum) {
sha256: sha2.Sha256,
sha384: sha2.Sha384,
sha512: sha2.Sha512,
pub fn init(mech: ck.CK_MECHANISM_TYPE) ?Hasher {
return switch (mech) {
ck.CKM_SHA256 => .{ .sha256 = sha2.Sha256.init(.{}) },
ck.CKM_SHA384 => .{ .sha384 = sha2.Sha384.init(.{}) },
ck.CKM_SHA512 => .{ .sha512 = sha2.Sha512.init(.{}) },
else => null,
};
}
pub fn update(self: *Hasher, data: []const u8) void {
switch (self.*) {
inline else => |*h| h.update(data),
}
}
pub fn digestLen(self: *const Hasher) usize {
return switch (self.*) {
inline else => |h| @TypeOf(h).digest_length,
};
}
pub fn finalInto(self: *Hasher, out: []u8) void {
switch (self.*) {
inline else => |*h| {
const Hash = @TypeOf(h.*);
h.final(out[0..Hash.digest_length]);
},
}
}
};
pub fn digestLenOf(mech: ck.CK_MECHANISM_TYPE) ?usize {
return switch (mech) {
ck.CKM_SHA256 => sha2.Sha256.digest_length,
ck.CKM_SHA384 => sha2.Sha384.digest_length,
ck.CKM_SHA512 => sha2.Sha512.digest_length,
else => null,
};
}
test "one-shot digest matches a known SHA-256 vector" {
var h = Hasher.init(ck.CKM_SHA256).?;
h.update("abc");
var out: [max_digest_len]u8 = undefined;
h.finalInto(&out);
const expect = [_]u8{
0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea,
0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c,
0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
};
try std.testing.expectEqual(@as(usize, 32), h.digestLen());
try std.testing.expectEqualSlices(u8, &expect, out[0..32]);
}
test "multi-part digest equals single-part" {
var a = Hasher.init(ck.CKM_SHA512).?;
a.update("hello world");
var oa: [max_digest_len]u8 = undefined;
a.finalInto(&oa);
var b = Hasher.init(ck.CKM_SHA512).?;
b.update("hello ");
b.update("world");
var ob: [max_digest_len]u8 = undefined;
b.finalInto(&ob);
try std.testing.expectEqualSlices(u8, oa[0..64], ob[0..64]);
}
test "unknown mechanism yields null" {
try std.testing.expect(Hasher.init(ck.CKM_AES_CBC) == null);
try std.testing.expect(digestLenOf(ck.CKM_SHA384).? == 48);
}

View File

@ -0,0 +1,420 @@
// ©AngelaMos | 2026
// ecdsa.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const digest = @import("digest.zig");
const P256 = std.crypto.sign.ecdsa.EcdsaP256Sha256;
const P384 = std.crypto.sign.ecdsa.EcdsaP384Sha384;
pub const max_scalar = P384.SecretKey.encoded_length;
pub const max_point = P384.PublicKey.uncompressed_sec1_encoded_length;
pub const max_sig = P384.Signature.encoded_length;
pub const max_prehash = digest.max_digest_len;
pub const max_ec_point_der = 2 + max_point;
const der_octet_string: u8 = 0x04;
const der_long_form_bit: u8 = 0x80;
const oid_p256 = [_]u8{ 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 };
const oid_p384 = [_]u8{ 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22 };
pub const Error = error{
Random,
Generate,
Crypto,
};
pub const Curve = enum {
p256,
p384,
pub fn scalarLen(self: Curve) usize {
return switch (self) {
.p256 => P256.SecretKey.encoded_length,
.p384 => P384.SecretKey.encoded_length,
};
}
pub fn pointLen(self: Curve) usize {
return switch (self) {
.p256 => P256.PublicKey.uncompressed_sec1_encoded_length,
.p384 => P384.PublicKey.uncompressed_sec1_encoded_length,
};
}
pub fn sigLen(self: Curve) usize {
return switch (self) {
.p256 => P256.Signature.encoded_length,
.p384 => P384.Signature.encoded_length,
};
}
pub fn bits(self: Curve) ck.CK_ULONG {
return switch (self) {
.p256 => config.ec_min_key_bits,
.p384 => config.ec_max_key_bits,
};
}
pub fn oidDer(self: Curve) []const u8 {
return switch (self) {
.p256 => &oid_p256,
.p384 => &oid_p384,
};
}
};
pub const HashMode = enum { raw, sha256 };
pub fn hashModeOf(mech: ck.CK_MECHANISM_TYPE) ?HashMode {
return switch (mech) {
ck.CKM_ECDSA => .raw,
ck.CKM_ECDSA_SHA256 => .sha256,
else => null,
};
}
pub fn curveFromParams(ec_params: []const u8) ?Curve {
if (std.mem.eql(u8, ec_params, &oid_p256)) return .p256;
if (std.mem.eql(u8, ec_params, &oid_p384)) return .p384;
return null;
}
pub fn wrapEcPoint(out: []u8, sec1: []const u8) []u8 {
out[0] = der_octet_string;
out[1] = @intCast(sec1.len);
@memcpy(out[2..][0..sec1.len], sec1);
return out[0 .. 2 + sec1.len];
}
pub fn unwrapEcPoint(der: []const u8) ?[]const u8 {
if (der.len < 2 or der[0] != der_octet_string) return null;
if (der[1] & der_long_form_bit == 0) {
const len: usize = der[1];
if (2 + len != der.len) return null;
return der[2 .. 2 + len];
}
const nlen: usize = der[1] & ~der_long_form_bit;
if (nlen == 0 or nlen > 2 or der.len < 2 + nlen) return null;
var len: usize = 0;
for (der[2 .. 2 + nlen]) |b| len = (len << 8) | b;
if (2 + nlen + len != der.len) return null;
return der[2 + nlen .. 2 + nlen + len];
}
pub const KeyMaterial = struct {
curve: Curve,
scalar: [max_scalar]u8 = @splat(0),
point: [max_point]u8 = @splat(0),
pub fn scalarBytes(self: *const KeyMaterial) []const u8 {
return self.scalar[0..self.curve.scalarLen()];
}
pub fn pointBytes(self: *const KeyMaterial) []const u8 {
return self.point[0..self.curve.pointLen()];
}
};
pub fn generate(io: std.Io, curve: Curve) Error!KeyMaterial {
return switch (curve) {
.p256 => generateImpl(P256, io, curve),
.p384 => generateImpl(P384, io, curve),
};
}
fn generateImpl(comptime Scheme: type, io: std.Io, curve: Curve) Error!KeyMaterial {
var attempt: usize = 0;
while (attempt < config.ec_keygen_max_attempts) : (attempt += 1) {
var seed: [Scheme.KeyPair.seed_length]u8 = undefined;
defer std.crypto.secureZero(u8, &seed);
io.randomSecure(&seed) catch return Error.Random;
const kp = Scheme.KeyPair.generateDeterministic(seed) catch continue;
var km: KeyMaterial = .{ .curve = curve };
const sk = kp.secret_key.toBytes();
@memcpy(km.scalar[0..sk.len], &sk);
const pt = kp.public_key.toUncompressedSec1();
@memcpy(km.point[0..pt.len], &pt);
return km;
}
return Error.Generate;
}
fn reduce(curve: Curve, dgst: []const u8, out: *[max_scalar]u8) []const u8 {
const n = curve.scalarLen();
@memset(out[0..n], 0);
if (dgst.len >= n) {
@memcpy(out[0..n], dgst[0..n]);
} else {
@memcpy(out[n - dgst.len .. n], dgst);
}
return out[0..n];
}
const Accum = struct {
mode: HashMode,
hasher: ?digest.Hasher = null,
raw: [max_prehash]u8 = @splat(0),
raw_len: usize = 0,
fn init(mode: HashMode) Accum {
return .{
.mode = mode,
.hasher = if (mode == .sha256) digest.Hasher.init(ck.CKM_SHA256) else null,
};
}
fn update(self: *Accum, data: []const u8) void {
switch (self.mode) {
.sha256 => self.hasher.?.update(data),
.raw => {
const take = @min(max_prehash - self.raw_len, data.len);
@memcpy(self.raw[self.raw_len..][0..take], data[0..take]);
self.raw_len += take;
},
}
}
fn digestBytes(self: *Accum, buf: *[max_prehash]u8) []const u8 {
switch (self.mode) {
.raw => return self.raw[0..self.raw_len],
.sha256 => {
const dlen = self.hasher.?.digestLen();
self.hasher.?.finalInto(buf[0..dlen]);
return buf[0..dlen];
},
}
}
};
pub const SignState = struct {
curve: Curve,
scalar: [max_scalar]u8 = @splat(0),
acc: Accum,
pub fn init(curve: Curve, mech: ck.CK_MECHANISM_TYPE, scalar: []const u8) ?SignState {
const mode = hashModeOf(mech) orelse return null;
if (scalar.len != curve.scalarLen()) return null;
var st: SignState = .{ .curve = curve, .acc = Accum.init(mode) };
@memcpy(st.scalar[0..scalar.len], scalar);
return st;
}
pub fn update(self: *SignState, data: []const u8) void {
self.acc.update(data);
}
pub fn sigLen(self: *const SignState) usize {
return self.curve.sigLen();
}
pub fn finalInto(self: *SignState, io: std.Io, out: []u8) Error!usize {
var dbuf: [max_prehash]u8 = undefined;
const dgst = self.acc.digestBytes(&dbuf);
var phbuf: [max_scalar]u8 = undefined;
const prehash = reduce(self.curve, dgst, &phbuf);
return switch (self.curve) {
.p256 => signImpl(P256, self.scalar[0..P256.SecretKey.encoded_length], prehash, io, out),
.p384 => signImpl(P384, self.scalar[0..P384.SecretKey.encoded_length], prehash, io, out),
};
}
};
fn signImpl(comptime Scheme: type, scalar: []const u8, prehash: []const u8, io: std.Io, out: []u8) Error!usize {
const slen = Scheme.SecretKey.encoded_length;
const siglen = Scheme.Signature.encoded_length;
var sk: [slen]u8 = undefined;
defer std.crypto.secureZero(u8, &sk);
@memcpy(&sk, scalar[0..slen]);
const kp = Scheme.KeyPair.fromSecretKey(.{ .bytes = sk }) catch return Error.Crypto;
var ph: [slen]u8 = undefined;
@memcpy(&ph, prehash[0..slen]);
var noise: [slen]u8 = undefined;
defer std.crypto.secureZero(u8, &noise);
const nz: ?[slen]u8 = if (io.randomSecure(&noise)) |_| noise else |_| null;
const sig = kp.signPrehashed(ph, nz) catch return Error.Crypto;
const raw = sig.toBytes();
@memcpy(out[0..siglen], &raw);
return siglen;
}
pub const VerifyResult = enum { ok, invalid, len_range };
pub const VerifyState = struct {
curve: Curve,
point: [max_point]u8 = @splat(0),
acc: Accum,
pub fn init(curve: Curve, mech: ck.CK_MECHANISM_TYPE, point_sec1: []const u8) ?VerifyState {
const mode = hashModeOf(mech) orelse return null;
if (point_sec1.len != curve.pointLen()) return null;
if (!validPoint(curve, point_sec1)) return null;
var st: VerifyState = .{ .curve = curve, .acc = Accum.init(mode) };
@memcpy(st.point[0..point_sec1.len], point_sec1);
return st;
}
pub fn update(self: *VerifyState, data: []const u8) void {
self.acc.update(data);
}
pub fn finalVerify(self: *VerifyState, sig: []const u8) VerifyResult {
if (sig.len != self.curve.sigLen()) return .len_range;
var dbuf: [max_prehash]u8 = undefined;
const dgst = self.acc.digestBytes(&dbuf);
var phbuf: [max_scalar]u8 = undefined;
const prehash = reduce(self.curve, dgst, &phbuf);
const point = self.point[0..self.curve.pointLen()];
return switch (self.curve) {
.p256 => verifyImpl(P256, point, prehash, sig),
.p384 => verifyImpl(P384, point, prehash, sig),
};
}
};
fn validPoint(curve: Curve, point_sec1: []const u8) bool {
switch (curve) {
.p256 => {
_ = P256.PublicKey.fromSec1(point_sec1) catch return false;
},
.p384 => {
_ = P384.PublicKey.fromSec1(point_sec1) catch return false;
},
}
return true;
}
fn verifyImpl(comptime Scheme: type, point: []const u8, prehash: []const u8, sig: []const u8) VerifyResult {
const slen = Scheme.SecretKey.encoded_length;
const siglen = Scheme.Signature.encoded_length;
const pk = Scheme.PublicKey.fromSec1(point) catch return .invalid;
var sb: [siglen]u8 = undefined;
@memcpy(&sb, sig[0..siglen]);
const signature = Scheme.Signature.fromBytes(sb);
var ph: [slen]u8 = undefined;
@memcpy(&ph, prehash[0..slen]);
signature.verifyPrehashed(ph, pk) catch return .invalid;
return .ok;
}
fn hexToBytes(comptime hex: []const u8) [hex.len / 2]u8 {
var out: [hex.len / 2]u8 = undefined;
_ = std.fmt.hexToBytes(&out, hex) catch unreachable;
return out;
}
test "RFC 6979 P-256/SHA-256 sample vector verifies and rejects tampering" {
const ux = hexToBytes("60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6");
const uy = hexToBytes("7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299");
const point = [_]u8{0x04} ++ ux ++ uy;
var sig = hexToBytes("EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716") ++
hexToBytes("F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8");
var v = VerifyState.init(.p256, ck.CKM_ECDSA_SHA256, &point).?;
v.update("sample");
try std.testing.expectEqual(VerifyResult.ok, v.finalVerify(&sig));
sig[0] ^= 0x01;
var v2 = VerifyState.init(.p256, ck.CKM_ECDSA_SHA256, &point).?;
v2.update("sample");
try std.testing.expectEqual(VerifyResult.invalid, v2.finalVerify(&sig));
}
test "P-256 generate then hash-then-sign round-trips and detects tamper" {
const io = std.testing.io;
const km = try generate(io, .p256);
var s = SignState.init(.p256, ck.CKM_ECDSA_SHA256, km.scalarBytes()).?;
s.update("attack at dawn");
var sig: [max_sig]u8 = undefined;
const n = try s.finalInto(io, &sig);
try std.testing.expectEqual(@as(usize, 64), n);
var v = VerifyState.init(.p256, ck.CKM_ECDSA_SHA256, km.pointBytes()).?;
v.update("attack at dawn");
try std.testing.expectEqual(VerifyResult.ok, v.finalVerify(sig[0..n]));
var v2 = VerifyState.init(.p256, ck.CKM_ECDSA_SHA256, km.pointBytes()).?;
v2.update("attack at dusk");
try std.testing.expectEqual(VerifyResult.invalid, v2.finalVerify(sig[0..n]));
}
test "P-256 raw prehash signing round-trips" {
const io = std.testing.io;
const km = try generate(io, .p256);
const hash = [_]u8{0xab} ** 32;
var s = SignState.init(.p256, ck.CKM_ECDSA, km.scalarBytes()).?;
s.update(&hash);
var sig: [max_sig]u8 = undefined;
const n = try s.finalInto(io, &sig);
var v = VerifyState.init(.p256, ck.CKM_ECDSA, km.pointBytes()).?;
v.update(&hash);
try std.testing.expectEqual(VerifyResult.ok, v.finalVerify(sig[0..n]));
}
test "P-384 generate sign verify round-trips with correct sizes" {
const io = std.testing.io;
const km = try generate(io, .p384);
try std.testing.expectEqual(@as(usize, 48), km.scalarBytes().len);
try std.testing.expectEqual(@as(usize, 97), km.pointBytes().len);
var s = SignState.init(.p384, ck.CKM_ECDSA_SHA256, km.scalarBytes()).?;
s.update("p384 message");
var sig: [max_sig]u8 = undefined;
const n = try s.finalInto(io, &sig);
try std.testing.expectEqual(@as(usize, 96), n);
var v = VerifyState.init(.p384, ck.CKM_ECDSA_SHA256, km.pointBytes()).?;
v.update("p384 message");
try std.testing.expectEqual(VerifyResult.ok, v.finalVerify(sig[0..n]));
}
test "P-384 raw prehash signing round-trips" {
const io = std.testing.io;
const km = try generate(io, .p384);
const hash = [_]u8{0xcd} ** 48;
var s = SignState.init(.p384, ck.CKM_ECDSA, km.scalarBytes()).?;
s.update(&hash);
var sig: [max_sig]u8 = undefined;
const n = try s.finalInto(io, &sig);
try std.testing.expectEqual(@as(usize, 96), n);
var v = VerifyState.init(.p384, ck.CKM_ECDSA, km.pointBytes()).?;
v.update(&hash);
try std.testing.expectEqual(VerifyResult.ok, v.finalVerify(sig[0..n]));
}
test "wrong-length signature reports len_range" {
const io = std.testing.io;
const km = try generate(io, .p256);
var v = VerifyState.init(.p256, ck.CKM_ECDSA_SHA256, km.pointBytes()).?;
v.update("data");
try std.testing.expectEqual(VerifyResult.len_range, v.finalVerify(&[_]u8{0} ** 63));
}
test "curve OID mapping and EC point DER round-trip" {
try std.testing.expectEqual(Curve.p256, curveFromParams(&oid_p256).?);
try std.testing.expectEqual(Curve.p384, curveFromParams(&oid_p384).?);
try std.testing.expect(curveFromParams(&[_]u8{ 0x06, 0x01, 0x00 }) == null);
const sec1 = [_]u8{0x04} ++ [_]u8{0x11} ** 64;
var buf: [max_ec_point_der]u8 = undefined;
const der = wrapEcPoint(&buf, &sec1);
try std.testing.expectEqual(@as(usize, 67), der.len);
try std.testing.expectEqualSlices(u8, &sec1, unwrapEcPoint(der).?);
try std.testing.expect(unwrapEcPoint(&[_]u8{0x05}) == null);
}

View File

@ -0,0 +1,145 @@
// ©AngelaMos | 2026
// keystore.zig
const std = @import("std");
const config = @import("../config.zig");
const pin = @import("pin.zig");
const gcm = std.crypto.aead.aes_gcm.Aes256Gcm;
pub const mk_len = config.master_key_len;
pub const nonce_len = config.gcm_iv_len;
pub const tag_len = config.gcm_tag_len;
pub const seal_overhead = nonce_len + tag_len;
pub const MasterKey = [mk_len]u8;
pub const Salt = pin.Salt;
pub const Error = error{ Malformed, AuthFailed };
pub const Wrapped = struct {
salt: Salt = @splat(0),
nonce: [nonce_len]u8 = @splat(0),
ct: [mk_len]u8 = @splat(0),
tag: [tag_len]u8 = @splat(0),
};
pub fn deriveKek(io: std.Io, allocator: std.mem.Allocator, pin_bytes: []const u8, salt: *const Salt, out: *MasterKey) !void {
try pin.derive(io, allocator, pin_bytes, salt, out);
}
pub fn generateMasterKey(io: std.Io, out: *MasterKey) !void {
try io.randomSecure(out);
}
pub fn wrap(io: std.Io, allocator: std.mem.Allocator, pin_bytes: []const u8, mk: *const MasterKey) !Wrapped {
var w: Wrapped = .{};
try pin.genSalt(io, &w.salt);
var kek: MasterKey = undefined;
defer std.crypto.secureZero(u8, &kek);
try deriveKek(io, allocator, pin_bytes, &w.salt, &kek);
try io.randomSecure(&w.nonce);
gcm.encrypt(&w.ct, &w.tag, mk, "", w.nonce, kek);
return w;
}
pub fn rewrap(io: std.Io, allocator: std.mem.Allocator, pin_bytes: []const u8, mk: *const MasterKey) !Wrapped {
return wrap(io, allocator, pin_bytes, mk);
}
pub fn unwrap(io: std.Io, allocator: std.mem.Allocator, pin_bytes: []const u8, w: *const Wrapped, out: *MasterKey) !bool {
var kek: MasterKey = undefined;
defer std.crypto.secureZero(u8, &kek);
try deriveKek(io, allocator, pin_bytes, &w.salt, &kek);
gcm.decrypt(out, &w.ct, w.tag, "", w.nonce, kek) catch {
std.crypto.secureZero(u8, out);
return false;
};
return true;
}
pub fn sealedLen(plain_len: usize) usize {
return nonce_len + plain_len + tag_len;
}
pub fn seal(io: std.Io, mk: *const MasterKey, ad: []const u8, plain: []const u8, out: []u8) !usize {
var nonce: [nonce_len]u8 = undefined;
try io.randomSecure(&nonce);
@memcpy(out[0..nonce_len], &nonce);
const ct = out[nonce_len..][0..plain.len];
const tag = out[nonce_len + plain.len ..][0..tag_len];
gcm.encrypt(ct, tag, plain, ad, nonce, mk.*);
return sealedLen(plain.len);
}
pub fn unseal(mk: *const MasterKey, ad: []const u8, sealed: []const u8, out: []u8) Error!usize {
if (sealed.len < seal_overhead) return Error.Malformed;
const ct_len = sealed.len - seal_overhead;
var nonce: [nonce_len]u8 = undefined;
@memcpy(&nonce, sealed[0..nonce_len]);
var tag: [tag_len]u8 = undefined;
@memcpy(&tag, sealed[nonce_len + ct_len ..][0..tag_len]);
const ct = sealed[nonce_len..][0..ct_len];
gcm.decrypt(out[0..ct_len], ct, tag, ad, nonce, mk.*) catch return Error.AuthFailed;
return ct_len;
}
test "master key wrap then unwrap round-trips under the right PIN" {
const io = std.testing.io;
const a = std.testing.allocator;
var mk: MasterKey = undefined;
try generateMasterKey(io, &mk);
const w = try wrap(io, a, "1234", &mk);
var got: MasterKey = undefined;
try std.testing.expect(try unwrap(io, a, "1234", &w, &got));
try std.testing.expectEqualSlices(u8, &mk, &got);
}
test "unwrap with the wrong PIN fails the GCM tag and yields false" {
const io = std.testing.io;
const a = std.testing.allocator;
var mk: MasterKey = undefined;
try generateMasterKey(io, &mk);
const w = try wrap(io, a, "1234", &mk);
var got: MasterKey = undefined;
try std.testing.expect(!try unwrap(io, a, "9999", &w, &got));
}
test "rewrap under a new PIN keeps the same master key recoverable" {
const io = std.testing.io;
const a = std.testing.allocator;
var mk: MasterKey = undefined;
try generateMasterKey(io, &mk);
const w1 = try wrap(io, a, "old-pin", &mk);
var unwrapped: MasterKey = undefined;
try std.testing.expect(try unwrap(io, a, "old-pin", &w1, &unwrapped));
const w2 = try rewrap(io, a, "new-pin", &unwrapped);
var got: MasterKey = undefined;
try std.testing.expect(!try unwrap(io, a, "old-pin", &w2, &got));
try std.testing.expect(try unwrap(io, a, "new-pin", &w2, &got));
try std.testing.expectEqualSlices(u8, &mk, &got);
}
test "seal then unseal round-trips and binds the AAD" {
const io = std.testing.io;
var mk: MasterKey = undefined;
try generateMasterKey(io, &mk);
const secret = "private-scalar-bytes";
const ad = "\x11\x00\x00\x00\x00\x00\x00\x00";
var sealed: [64]u8 = undefined;
const sn = try seal(io, &mk, ad, secret, &sealed);
try std.testing.expectEqual(sealedLen(secret.len), sn);
var out: [64]u8 = undefined;
const un = try unseal(&mk, ad, sealed[0..sn], &out);
try std.testing.expectEqualSlices(u8, secret, out[0..un]);
try std.testing.expectError(Error.AuthFailed, unseal(&mk, "\x12\x00\x00\x00\x00\x00\x00\x00", sealed[0..sn], &out));
sealed[0] ^= 0x01;
try std.testing.expectError(Error.AuthFailed, unseal(&mk, ad, sealed[0..sn], &out));
}

View File

@ -0,0 +1,76 @@
// ©AngelaMos | 2026
// mac.zig
const std = @import("std");
const ck = @import("../ck.zig");
const hmac = std.crypto.auth.hmac.sha2;
pub const max_mac_len = hmac.HmacSha512.mac_length;
pub const Mac = union(enum) {
sha256: hmac.HmacSha256,
sha384: hmac.HmacSha384,
sha512: hmac.HmacSha512,
pub fn init(mech: ck.CK_MECHANISM_TYPE, key: []const u8) ?Mac {
return switch (mech) {
ck.CKM_SHA256_HMAC => .{ .sha256 = hmac.HmacSha256.init(key) },
ck.CKM_SHA384_HMAC => .{ .sha384 = hmac.HmacSha384.init(key) },
ck.CKM_SHA512_HMAC => .{ .sha512 = hmac.HmacSha512.init(key) },
else => null,
};
}
pub fn update(self: *Mac, data: []const u8) void {
switch (self.*) {
inline else => |*m| m.update(data),
}
}
pub fn macLen(self: *const Mac) usize {
return switch (self.*) {
inline else => |m| @TypeOf(m).mac_length,
};
}
pub fn finalInto(self: *Mac, out: []u8) void {
switch (self.*) {
inline else => |*m| {
const M = @TypeOf(m.*);
m.final(out[0..M.mac_length]);
},
}
}
};
pub fn macLenOf(mech: ck.CK_MECHANISM_TYPE) ?usize {
return switch (mech) {
ck.CKM_SHA256_HMAC => hmac.HmacSha256.mac_length,
ck.CKM_SHA384_HMAC => hmac.HmacSha384.mac_length,
ck.CKM_SHA512_HMAC => hmac.HmacSha512.mac_length,
else => null,
};
}
test "HMAC-SHA256 matches RFC 4231 test case 2" {
var m = Mac.init(ck.CKM_SHA256_HMAC, "Jefe").?;
m.update("what do ya want ");
m.update("for nothing?");
var out: [max_mac_len]u8 = undefined;
m.finalInto(&out);
const expect = [_]u8{
0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e,
0x6a, 0x04, 0x24, 0x26, 0x08, 0x95, 0x75, 0xc7,
0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, 0x39, 0x83,
0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec, 0x38, 0x43,
};
try std.testing.expectEqual(@as(usize, 32), m.macLen());
try std.testing.expectEqualSlices(u8, &expect, out[0..32]);
}
test "macLenOf maps mechanisms and rejects non-HMAC" {
try std.testing.expectEqual(@as(?usize, 48), macLenOf(ck.CKM_SHA384_HMAC));
try std.testing.expectEqual(@as(?usize, 64), macLenOf(ck.CKM_SHA512_HMAC));
try std.testing.expect(macLenOf(ck.CKM_SHA256) == null);
}

View File

@ -0,0 +1,85 @@
// ©AngelaMos | 2026
// openssl.zig
pub const EVP_PKEY = opaque {};
pub const EVP_PKEY_CTX = opaque {};
pub const EVP_MD = opaque {};
pub const EVP_MD_CTX = opaque {};
pub const ENGINE = opaque {};
pub const BIGNUM = opaque {};
pub const OSSL_LIB_CTX = opaque {};
pub const OSSL_PARAM = opaque {};
pub const OSSL_PARAM_BLD = opaque {};
pub const pkey_rsa: c_int = 6;
pub const selection_public_key: c_int = 134;
pub const selection_keypair: c_int = 135;
pub const pad_pkcs1: c_int = 1;
pub const pad_oaep: c_int = 4;
pub const pad_pss: c_int = 6;
pub const pss_saltlen_digest: c_int = -1;
pub const param_n = "n";
pub const param_e = "e";
pub const param_d = "d";
pub const param_factor1 = "rsa-factor1";
pub const param_factor2 = "rsa-factor2";
pub const param_exponent1 = "rsa-exponent1";
pub const param_exponent2 = "rsa-exponent2";
pub const param_coefficient1 = "rsa-coefficient1";
pub extern fn EVP_PKEY_CTX_new_id(id: c_int, e: ?*ENGINE) ?*EVP_PKEY_CTX;
pub extern fn EVP_PKEY_CTX_new(pkey: ?*EVP_PKEY, e: ?*ENGINE) ?*EVP_PKEY_CTX;
pub extern fn EVP_PKEY_CTX_new_from_name(libctx: ?*OSSL_LIB_CTX, name: [*:0]const u8, propq: ?[*:0]const u8) ?*EVP_PKEY_CTX;
pub extern fn EVP_PKEY_CTX_free(ctx: ?*EVP_PKEY_CTX) void;
pub extern fn EVP_PKEY_free(pkey: ?*EVP_PKEY) void;
pub extern fn EVP_PKEY_keygen_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_CTX_set_rsa_keygen_bits(ctx: ?*EVP_PKEY_CTX, bits: c_int) c_int;
pub extern fn EVP_PKEY_generate(ctx: ?*EVP_PKEY_CTX, ppkey: *?*EVP_PKEY) c_int;
pub extern fn EVP_PKEY_get_bits(pkey: ?*const EVP_PKEY) c_int;
pub extern fn EVP_PKEY_get_bn_param(pkey: ?*const EVP_PKEY, key_name: [*:0]const u8, bn: *?*BIGNUM) c_int;
pub extern fn EVP_PKEY_fromdata_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_fromdata(ctx: ?*EVP_PKEY_CTX, ppkey: *?*EVP_PKEY, selection: c_int, params: ?*OSSL_PARAM) c_int;
pub extern fn BN_bin2bn(s: [*]const u8, len: c_int, ret: ?*BIGNUM) ?*BIGNUM;
pub extern fn BN_bn2binpad(a: ?*const BIGNUM, to: [*]u8, tolen: c_int) c_int;
pub extern fn BN_num_bits(a: ?*const BIGNUM) c_int;
pub extern fn BN_free(a: ?*BIGNUM) void;
pub extern fn BN_clear_free(a: ?*BIGNUM) void;
pub extern fn OSSL_PARAM_BLD_new() ?*OSSL_PARAM_BLD;
pub extern fn OSSL_PARAM_BLD_push_BN(bld: ?*OSSL_PARAM_BLD, key: [*:0]const u8, bn: ?*const BIGNUM) c_int;
pub extern fn OSSL_PARAM_BLD_to_param(bld: ?*OSSL_PARAM_BLD) ?*OSSL_PARAM;
pub extern fn OSSL_PARAM_BLD_free(bld: ?*OSSL_PARAM_BLD) void;
pub extern fn OSSL_PARAM_free(p: ?*OSSL_PARAM) void;
pub extern fn EVP_MD_CTX_new() ?*EVP_MD_CTX;
pub extern fn EVP_MD_CTX_free(ctx: ?*EVP_MD_CTX) void;
pub extern fn EVP_sha256() ?*const EVP_MD;
pub extern fn EVP_sha384() ?*const EVP_MD;
pub extern fn EVP_sha512() ?*const EVP_MD;
pub extern fn EVP_DigestSignInit(ctx: ?*EVP_MD_CTX, pctx: ?*?*EVP_PKEY_CTX, mdtype: ?*const EVP_MD, e: ?*ENGINE, pkey: ?*EVP_PKEY) c_int;
pub extern fn EVP_DigestSign(ctx: ?*EVP_MD_CTX, sigret: ?[*]u8, siglen: *usize, tbs: [*]const u8, tbslen: usize) c_int;
pub extern fn EVP_DigestVerifyInit(ctx: ?*EVP_MD_CTX, pctx: ?*?*EVP_PKEY_CTX, mdtype: ?*const EVP_MD, e: ?*ENGINE, pkey: ?*EVP_PKEY) c_int;
pub extern fn EVP_DigestVerify(ctx: ?*EVP_MD_CTX, sig: [*]const u8, siglen: usize, tbs: [*]const u8, tbslen: usize) c_int;
pub extern fn EVP_PKEY_sign_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_sign(ctx: ?*EVP_PKEY_CTX, sig: ?[*]u8, siglen: *usize, tbs: [*]const u8, tbslen: usize) c_int;
pub extern fn EVP_PKEY_verify_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_verify(ctx: ?*EVP_PKEY_CTX, sig: [*]const u8, siglen: usize, tbs: [*]const u8, tbslen: usize) c_int;
pub extern fn EVP_PKEY_encrypt_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_encrypt(ctx: ?*EVP_PKEY_CTX, out: ?[*]u8, outlen: *usize, in: [*]const u8, inlen: usize) c_int;
pub extern fn EVP_PKEY_decrypt_init(ctx: ?*EVP_PKEY_CTX) c_int;
pub extern fn EVP_PKEY_decrypt(ctx: ?*EVP_PKEY_CTX, out: ?[*]u8, outlen: *usize, in: [*]const u8, inlen: usize) c_int;
pub extern fn EVP_PKEY_CTX_set_rsa_padding(ctx: ?*EVP_PKEY_CTX, pad_mode: c_int) c_int;
pub extern fn EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx: ?*EVP_PKEY_CTX, saltlen: c_int) c_int;
pub extern fn EVP_PKEY_CTX_set_rsa_mgf1_md(ctx: ?*EVP_PKEY_CTX, md: ?*const EVP_MD) c_int;
pub extern fn EVP_PKEY_CTX_set_rsa_oaep_md(ctx: ?*EVP_PKEY_CTX, md: ?*const EVP_MD) c_int;
pub extern fn EVP_PKEY_CTX_set_signature_md(ctx: ?*EVP_PKEY_CTX, md: ?*const EVP_MD) c_int;

View File

@ -0,0 +1,77 @@
// ©AngelaMos | 2026
// pin.zig
const std = @import("std");
const config = @import("../config.zig");
const argon2 = std.crypto.pwhash.argon2;
pub const salt_len = config.pin_salt_len;
pub const hash_len = config.pin_hash_len;
pub const Salt = [salt_len]u8;
pub const Hash = [hash_len]u8;
const params: argon2.Params = .{
.t = config.pin_kdf_t,
.m = config.pin_kdf_m_kib,
.p = config.pin_kdf_p,
};
pub fn genSalt(io: std.Io, out: *Salt) !void {
try io.randomSecure(out);
}
pub fn derive(io: std.Io, allocator: std.mem.Allocator, pin: []const u8, salt: *const Salt, out: *Hash) !void {
try argon2.kdf(allocator, out, pin, salt, params, .argon2id, io);
}
pub fn verify(io: std.Io, allocator: std.mem.Allocator, pin: []const u8, salt: *const Salt, expected: *const Hash) !bool {
var got: Hash = undefined;
defer std.crypto.secureZero(u8, &got);
try derive(io, allocator, pin, salt, &got);
return std.crypto.timing_safe.eql(Hash, got, expected.*);
}
test "argon2id derive is deterministic for a fixed salt" {
var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
defer threaded.deinit();
const io = threaded.io();
const salt: Salt = @splat(7);
var a: Hash = undefined;
var b: Hash = undefined;
try derive(io, std.testing.allocator, "1234", &salt, &a);
try derive(io, std.testing.allocator, "1234", &salt, &b);
try std.testing.expectEqual(a, b);
}
test "verify accepts the right PIN and rejects the wrong one" {
var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
defer threaded.deinit();
const io = threaded.io();
var salt: Salt = undefined;
try genSalt(io, &salt);
var h: Hash = undefined;
try derive(io, std.testing.allocator, "secret-pin", &salt, &h);
try std.testing.expect(try verify(io, std.testing.allocator, "secret-pin", &salt, &h));
try std.testing.expect(!try verify(io, std.testing.allocator, "wrong-pin", &salt, &h));
}
test "a fresh salt changes the derived hash" {
var threaded: std.Io.Threaded = .init(std.testing.allocator, .{});
defer threaded.deinit();
const io = threaded.io();
var s1: Salt = undefined;
var s2: Salt = undefined;
try genSalt(io, &s1);
try genSalt(io, &s2);
var h1: Hash = undefined;
var h2: Hash = undefined;
try derive(io, std.testing.allocator, "1234", &s1, &h1);
try derive(io, std.testing.allocator, "1234", &s2, &h2);
try std.testing.expect(!std.mem.eql(u8, &h1, &h2));
}

View File

@ -0,0 +1,354 @@
// ©AngelaMos | 2026
// rsa.zig
const std = @import("std");
const ck = @import("../ck.zig");
const config = @import("../config.zig");
const ossl = @import("openssl.zig");
pub const max_modulus_bytes: usize = config.rsa_max_key_bits / 8;
pub const max_sig_bytes: usize = max_modulus_bytes;
const component_count = 8;
pub const Error = error{Crypto};
pub const Hash = enum {
none,
sha256,
sha384,
sha512,
pub fn fromMech(mech: ck.CK_MECHANISM_TYPE) ?Hash {
return switch (mech) {
ck.CKM_SHA256 => .sha256,
ck.CKM_SHA384 => .sha384,
ck.CKM_SHA512 => .sha512,
else => null,
};
}
};
fn mdOf(h: Hash) ?*const ossl.EVP_MD {
return switch (h) {
.none => null,
.sha256 => ossl.EVP_sha256(),
.sha384 => ossl.EVP_sha384(),
.sha512 => ossl.EVP_sha512(),
};
}
pub const SigScheme = enum { pkcs1, pss };
pub const SignParams = struct {
scheme: SigScheme,
digest: Hash,
pss_hash: Hash = .sha256,
salt_len: c_int = ossl.pss_saltlen_digest,
};
pub const CryptScheme = enum { pkcs1, oaep };
pub const CryptParams = struct {
scheme: CryptScheme,
oaep_hash: Hash = .sha256,
};
pub const VerifyResult = enum { ok, invalid };
pub const Buf = struct {
bytes: [max_modulus_bytes]u8 = @splat(0),
len: usize = 0,
pub fn slice(self: *const Buf) []const u8 {
return self.bytes[0..self.len];
}
};
pub const Generated = struct {
bits: u32 = 0,
n: Buf = .{},
e: Buf = .{},
d: Buf = .{},
p: Buf = .{},
q: Buf = .{},
dmp1: Buf = .{},
dmq1: Buf = .{},
iqmp: Buf = .{},
pub fn zeroize(self: *Generated) void {
std.crypto.secureZero(u8, &self.d.bytes);
std.crypto.secureZero(u8, &self.p.bytes);
std.crypto.secureZero(u8, &self.q.bytes);
std.crypto.secureZero(u8, &self.dmp1.bytes);
std.crypto.secureZero(u8, &self.dmq1.bytes);
std.crypto.secureZero(u8, &self.iqmp.bytes);
}
};
pub const PublicComponents = struct {
n: []const u8,
e: []const u8,
};
pub const PrivateComponents = struct {
n: []const u8,
e: []const u8,
d: []const u8,
p: []const u8,
q: []const u8,
dmp1: []const u8,
dmq1: []const u8,
iqmp: []const u8,
};
fn extractBn(pkey: *const ossl.EVP_PKEY, name: [*:0]const u8, buf: *Buf) Error!void {
var bn: ?*ossl.BIGNUM = null;
if (ossl.EVP_PKEY_get_bn_param(pkey, name, &bn) <= 0) return Error.Crypto;
defer ossl.BN_clear_free(bn);
const nbits = ossl.BN_num_bits(bn);
if (nbits < 0) return Error.Crypto;
const nbytes: usize = @intCast(@divFloor(nbits + 7, 8));
if (nbytes == 0 or nbytes > max_modulus_bytes) return Error.Crypto;
if (ossl.BN_bn2binpad(bn, &buf.bytes, @intCast(nbytes)) < 0) return Error.Crypto;
buf.len = nbytes;
}
pub fn generate(bits: u32) Error!Generated {
const ctx = ossl.EVP_PKEY_CTX_new_id(ossl.pkey_rsa, null) orelse return Error.Crypto;
defer ossl.EVP_PKEY_CTX_free(ctx);
if (ossl.EVP_PKEY_keygen_init(ctx) <= 0) return Error.Crypto;
if (ossl.EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, @intCast(bits)) <= 0) return Error.Crypto;
var pkey: ?*ossl.EVP_PKEY = null;
if (ossl.EVP_PKEY_generate(ctx, &pkey) <= 0) return Error.Crypto;
defer ossl.EVP_PKEY_free(pkey);
const key = pkey orelse return Error.Crypto;
var g: Generated = .{ .bits = @intCast(ossl.EVP_PKEY_get_bits(key)) };
errdefer g.zeroize();
try extractBn(key, ossl.param_n, &g.n);
try extractBn(key, ossl.param_e, &g.e);
try extractBn(key, ossl.param_d, &g.d);
try extractBn(key, ossl.param_factor1, &g.p);
try extractBn(key, ossl.param_factor2, &g.q);
try extractBn(key, ossl.param_exponent1, &g.dmp1);
try extractBn(key, ossl.param_exponent2, &g.dmq1);
try extractBn(key, ossl.param_coefficient1, &g.iqmp);
return g;
}
fn buildKey(names: []const [*:0]const u8, vals: []const []const u8, selection: c_int) Error!*ossl.EVP_PKEY {
const bld = ossl.OSSL_PARAM_BLD_new() orelse return Error.Crypto;
defer ossl.OSSL_PARAM_BLD_free(bld);
var bns: [component_count]?*ossl.BIGNUM = @splat(null);
defer for (bns[0..names.len]) |bn| ossl.BN_clear_free(bn);
for (names, vals, 0..) |nm, v, i| {
bns[i] = ossl.BN_bin2bn(v.ptr, @intCast(v.len), null) orelse return Error.Crypto;
if (ossl.OSSL_PARAM_BLD_push_BN(bld, nm, bns[i]) <= 0) return Error.Crypto;
}
const params = ossl.OSSL_PARAM_BLD_to_param(bld) orelse return Error.Crypto;
defer ossl.OSSL_PARAM_free(params);
const ctx = ossl.EVP_PKEY_CTX_new_from_name(null, "RSA", null) orelse return Error.Crypto;
defer ossl.EVP_PKEY_CTX_free(ctx);
if (ossl.EVP_PKEY_fromdata_init(ctx) <= 0) return Error.Crypto;
var pkey: ?*ossl.EVP_PKEY = null;
if (ossl.EVP_PKEY_fromdata(ctx, &pkey, selection, params) <= 0) return Error.Crypto;
return pkey orelse Error.Crypto;
}
fn buildPublic(pc: PublicComponents) Error!*ossl.EVP_PKEY {
return buildKey(
&.{ ossl.param_n, ossl.param_e },
&.{ pc.n, pc.e },
ossl.selection_public_key,
);
}
fn buildPrivate(sc: PrivateComponents) Error!*ossl.EVP_PKEY {
return buildKey(
&.{ ossl.param_n, ossl.param_e, ossl.param_d, ossl.param_factor1, ossl.param_factor2, ossl.param_exponent1, ossl.param_exponent2, ossl.param_coefficient1 },
&.{ sc.n, sc.e, sc.d, sc.p, sc.q, sc.dmp1, sc.dmq1, sc.iqmp },
ossl.selection_keypair,
);
}
fn applyPss(pctx: ?*ossl.EVP_PKEY_CTX, p: SignParams, raw: bool) Error!void {
if (ossl.EVP_PKEY_CTX_set_rsa_padding(pctx, ossl.pad_pss) <= 0) return Error.Crypto;
if (raw) {
if (ossl.EVP_PKEY_CTX_set_signature_md(pctx, mdOf(p.pss_hash)) <= 0) return Error.Crypto;
}
if (ossl.EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, p.salt_len) <= 0) return Error.Crypto;
if (ossl.EVP_PKEY_CTX_set_rsa_mgf1_md(pctx, mdOf(p.pss_hash)) <= 0) return Error.Crypto;
}
pub fn sign(sc: PrivateComponents, p: SignParams, data: []const u8, out: []u8) Error!usize {
const pkey = try buildPrivate(sc);
defer ossl.EVP_PKEY_free(pkey);
var siglen: usize = out.len;
if (p.digest != .none) {
const mdctx = ossl.EVP_MD_CTX_new() orelse return Error.Crypto;
defer ossl.EVP_MD_CTX_free(mdctx);
var pctx: ?*ossl.EVP_PKEY_CTX = null;
if (ossl.EVP_DigestSignInit(mdctx, &pctx, mdOf(p.digest), null, pkey) <= 0) return Error.Crypto;
if (p.scheme == .pss) try applyPss(pctx, p, false);
if (ossl.EVP_DigestSign(mdctx, out.ptr, &siglen, data.ptr, data.len) <= 0) return Error.Crypto;
return siglen;
}
const ctx = ossl.EVP_PKEY_CTX_new(pkey, null) orelse return Error.Crypto;
defer ossl.EVP_PKEY_CTX_free(ctx);
if (ossl.EVP_PKEY_sign_init(ctx) <= 0) return Error.Crypto;
if (p.scheme == .pss) {
try applyPss(ctx, p, true);
} else {
if (ossl.EVP_PKEY_CTX_set_rsa_padding(ctx, ossl.pad_pkcs1) <= 0) return Error.Crypto;
}
if (ossl.EVP_PKEY_sign(ctx, out.ptr, &siglen, data.ptr, data.len) <= 0) return Error.Crypto;
return siglen;
}
pub fn verify(pc: PublicComponents, p: SignParams, data: []const u8, sig: []const u8) Error!VerifyResult {
const pkey = try buildPublic(pc);
defer ossl.EVP_PKEY_free(pkey);
if (p.digest != .none) {
const mdctx = ossl.EVP_MD_CTX_new() orelse return Error.Crypto;
defer ossl.EVP_MD_CTX_free(mdctx);
var pctx: ?*ossl.EVP_PKEY_CTX = null;
if (ossl.EVP_DigestVerifyInit(mdctx, &pctx, mdOf(p.digest), null, pkey) <= 0) return Error.Crypto;
if (p.scheme == .pss) try applyPss(pctx, p, false);
return if (ossl.EVP_DigestVerify(mdctx, sig.ptr, sig.len, data.ptr, data.len) == 1) .ok else .invalid;
}
const ctx = ossl.EVP_PKEY_CTX_new(pkey, null) orelse return Error.Crypto;
defer ossl.EVP_PKEY_CTX_free(ctx);
if (ossl.EVP_PKEY_verify_init(ctx) <= 0) return Error.Crypto;
if (p.scheme == .pss) {
try applyPss(ctx, p, true);
} else {
if (ossl.EVP_PKEY_CTX_set_rsa_padding(ctx, ossl.pad_pkcs1) <= 0) return Error.Crypto;
}
return if (ossl.EVP_PKEY_verify(ctx, sig.ptr, sig.len, data.ptr, data.len) == 1) .ok else .invalid;
}
fn applyCryptPadding(ctx: ?*ossl.EVP_PKEY_CTX, p: CryptParams) Error!void {
if (p.scheme == .oaep) {
if (ossl.EVP_PKEY_CTX_set_rsa_padding(ctx, ossl.pad_oaep) <= 0) return Error.Crypto;
if (ossl.EVP_PKEY_CTX_set_rsa_oaep_md(ctx, mdOf(p.oaep_hash)) <= 0) return Error.Crypto;
if (ossl.EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, mdOf(p.oaep_hash)) <= 0) return Error.Crypto;
} else {
if (ossl.EVP_PKEY_CTX_set_rsa_padding(ctx, ossl.pad_pkcs1) <= 0) return Error.Crypto;
}
}
pub fn encrypt(pc: PublicComponents, p: CryptParams, in: []const u8, out: []u8) Error!usize {
const pkey = try buildPublic(pc);
defer ossl.EVP_PKEY_free(pkey);
const ctx = ossl.EVP_PKEY_CTX_new(pkey, null) orelse return Error.Crypto;
defer ossl.EVP_PKEY_CTX_free(ctx);
if (ossl.EVP_PKEY_encrypt_init(ctx) <= 0) return Error.Crypto;
try applyCryptPadding(ctx, p);
var outlen: usize = out.len;
if (ossl.EVP_PKEY_encrypt(ctx, out.ptr, &outlen, in.ptr, in.len) <= 0) return Error.Crypto;
return outlen;
}
pub fn decrypt(sc: PrivateComponents, p: CryptParams, in: []const u8, out: []u8) Error!usize {
const pkey = try buildPrivate(sc);
defer ossl.EVP_PKEY_free(pkey);
const ctx = ossl.EVP_PKEY_CTX_new(pkey, null) orelse return Error.Crypto;
defer ossl.EVP_PKEY_CTX_free(ctx);
if (ossl.EVP_PKEY_decrypt_init(ctx) <= 0) return Error.Crypto;
try applyCryptPadding(ctx, p);
var outlen: usize = out.len;
if (ossl.EVP_PKEY_decrypt(ctx, out.ptr, &outlen, in.ptr, in.len) <= 0) return Error.Crypto;
return outlen;
}
fn testPriv(g: *const Generated) PrivateComponents {
return .{
.n = g.n.slice(),
.e = g.e.slice(),
.d = g.d.slice(),
.p = g.p.slice(),
.q = g.q.slice(),
.dmp1 = g.dmp1.slice(),
.dmq1 = g.dmq1.slice(),
.iqmp = g.iqmp.slice(),
};
}
fn testPub(g: *const Generated) PublicComponents {
return .{ .n = g.n.slice(), .e = g.e.slice() };
}
test "generate yields a 2048-bit key with sane component sizes" {
var g = try generate(config.rsa_min_key_bits);
defer g.zeroize();
try std.testing.expectEqual(@as(u32, 2048), g.bits);
try std.testing.expectEqual(@as(usize, 256), g.n.len);
try std.testing.expect(g.e.len >= 3 and g.e.len <= 4);
try std.testing.expect(g.p.len == 128 and g.q.len == 128);
}
test "PKCS#1 v1.5 hash-then-sign round-trips and detects tamper" {
var g = try generate(config.rsa_min_key_bits);
defer g.zeroize();
const params: SignParams = .{ .scheme = .pkcs1, .digest = .sha256 };
var sig: [max_sig_bytes]u8 = undefined;
const n = try sign(testPriv(&g), params, "enterprise message", &sig);
try std.testing.expectEqual(@as(usize, 256), n);
try std.testing.expectEqual(VerifyResult.ok, try verify(testPub(&g), params, "enterprise message", sig[0..n]));
try std.testing.expectEqual(VerifyResult.invalid, try verify(testPub(&g), params, "enterprise messagX", sig[0..n]));
sig[0] ^= 0x01;
try std.testing.expectEqual(VerifyResult.invalid, try verify(testPub(&g), params, "enterprise message", sig[0..n]));
}
test "PSS hash-then-sign round-trips" {
var g = try generate(config.rsa_min_key_bits);
defer g.zeroize();
const params: SignParams = .{ .scheme = .pss, .digest = .sha256, .pss_hash = .sha256 };
var sig: [max_sig_bytes]u8 = undefined;
const n = try sign(testPriv(&g), params, "pss payload", &sig);
try std.testing.expectEqual(VerifyResult.ok, try verify(testPub(&g), params, "pss payload", sig[0..n]));
try std.testing.expectEqual(VerifyResult.invalid, try verify(testPub(&g), params, "pss payloaX", sig[0..n]));
}
test "raw PKCS#1 v1.5 sign over a pre-hashed value round-trips" {
var g = try generate(config.rsa_min_key_bits);
defer g.zeroize();
const params: SignParams = .{ .scheme = .pkcs1, .digest = .none };
const prehash = [_]u8{0xa5} ** 32;
var sig: [max_sig_bytes]u8 = undefined;
const n = try sign(testPriv(&g), params, &prehash, &sig);
try std.testing.expectEqual(VerifyResult.ok, try verify(testPub(&g), params, &prehash, sig[0..n]));
}
test "PKCS#1 v1.5 encrypt/decrypt round-trips" {
var g = try generate(config.rsa_min_key_bits);
defer g.zeroize();
const params: CryptParams = .{ .scheme = .pkcs1 };
const msg = "wrap me";
var ct: [max_modulus_bytes]u8 = undefined;
const cn = try encrypt(testPub(&g), params, msg, &ct);
try std.testing.expectEqual(@as(usize, 256), cn);
var pt: [max_modulus_bytes]u8 = undefined;
const pn = try decrypt(testPriv(&g), params, ct[0..cn], &pt);
try std.testing.expectEqualSlices(u8, msg, pt[0..pn]);
}
test "OAEP-SHA256 encrypt/decrypt round-trips" {
var g = try generate(config.rsa_min_key_bits);
defer g.zeroize();
const params: CryptParams = .{ .scheme = .oaep, .oaep_hash = .sha256 };
const msg = "oaep secret payload";
var ct: [max_modulus_bytes]u8 = undefined;
const cn = try encrypt(testPub(&g), params, msg, &ct);
var pt: [max_modulus_bytes]u8 = undefined;
const pn = try decrypt(testPriv(&g), params, ct[0..cn], &pt);
try std.testing.expectEqualSlices(u8, msg, pt[0..pn]);
}

View File

@ -0,0 +1,94 @@
// ©AngelaMos | 2026
// main.zig
const std = @import("std");
const ck = @import("ck.zig");
const general = @import("api/general.zig");
const slot_token = @import("api/slot_token.zig");
const session = @import("api/session.zig");
const object = @import("api/object.zig");
const crypto_ops = @import("api/crypto_ops.zig");
const keymgmt = @import("api/keymgmt.zig");
const random = @import("api/random.zig");
comptime {
std.debug.assert(@sizeOf(ck.CK_FUNCTION_LIST) == 69 * @sizeOf(usize));
std.debug.assert(@sizeOf(ck.CK_ATTRIBUTE) == 24);
}
export fn C_GetFunctionList(ppFunctionList: *?*ck.CK_FUNCTION_LIST) callconv(.c) ck.CK_RV {
ppFunctionList.* = &function_list;
return ck.CKR_OK;
}
var function_list: ck.CK_FUNCTION_LIST = .{
.version = ck.CK_VERSION{ .major = 2, .minor = 40 },
.C_Initialize = general.C_Initialize,
.C_Finalize = general.C_Finalize,
.C_GetInfo = general.C_GetInfo,
.C_GetFunctionList = C_GetFunctionList,
.C_GetSlotList = slot_token.C_GetSlotList,
.C_GetSlotInfo = slot_token.C_GetSlotInfo,
.C_GetTokenInfo = slot_token.C_GetTokenInfo,
.C_GetMechanismList = slot_token.C_GetMechanismList,
.C_GetMechanismInfo = slot_token.C_GetMechanismInfo,
.C_InitToken = slot_token.C_InitToken,
.C_InitPIN = slot_token.C_InitPIN,
.C_SetPIN = slot_token.C_SetPIN,
.C_OpenSession = session.C_OpenSession,
.C_CloseSession = session.C_CloseSession,
.C_CloseAllSessions = session.C_CloseAllSessions,
.C_GetSessionInfo = session.C_GetSessionInfo,
.C_GetOperationState = session.C_GetOperationState,
.C_SetOperationState = session.C_SetOperationState,
.C_Login = session.C_Login,
.C_Logout = session.C_Logout,
.C_CreateObject = object.C_CreateObject,
.C_CopyObject = object.C_CopyObject,
.C_DestroyObject = object.C_DestroyObject,
.C_GetObjectSize = object.C_GetObjectSize,
.C_GetAttributeValue = object.C_GetAttributeValue,
.C_SetAttributeValue = object.C_SetAttributeValue,
.C_FindObjectsInit = object.C_FindObjectsInit,
.C_FindObjects = object.C_FindObjects,
.C_FindObjectsFinal = object.C_FindObjectsFinal,
.C_EncryptInit = crypto_ops.C_EncryptInit,
.C_Encrypt = crypto_ops.C_Encrypt,
.C_EncryptUpdate = crypto_ops.C_EncryptUpdate,
.C_EncryptFinal = crypto_ops.C_EncryptFinal,
.C_DecryptInit = crypto_ops.C_DecryptInit,
.C_Decrypt = crypto_ops.C_Decrypt,
.C_DecryptUpdate = crypto_ops.C_DecryptUpdate,
.C_DecryptFinal = crypto_ops.C_DecryptFinal,
.C_DigestInit = crypto_ops.C_DigestInit,
.C_Digest = crypto_ops.C_Digest,
.C_DigestUpdate = crypto_ops.C_DigestUpdate,
.C_DigestKey = crypto_ops.C_DigestKey,
.C_DigestFinal = crypto_ops.C_DigestFinal,
.C_SignInit = crypto_ops.C_SignInit,
.C_Sign = crypto_ops.C_Sign,
.C_SignUpdate = crypto_ops.C_SignUpdate,
.C_SignFinal = crypto_ops.C_SignFinal,
.C_SignRecoverInit = crypto_ops.C_SignRecoverInit,
.C_SignRecover = crypto_ops.C_SignRecover,
.C_VerifyInit = crypto_ops.C_VerifyInit,
.C_Verify = crypto_ops.C_Verify,
.C_VerifyUpdate = crypto_ops.C_VerifyUpdate,
.C_VerifyFinal = crypto_ops.C_VerifyFinal,
.C_VerifyRecoverInit = crypto_ops.C_VerifyRecoverInit,
.C_VerifyRecover = crypto_ops.C_VerifyRecover,
.C_DigestEncryptUpdate = crypto_ops.C_DigestEncryptUpdate,
.C_DecryptDigestUpdate = crypto_ops.C_DecryptDigestUpdate,
.C_SignEncryptUpdate = crypto_ops.C_SignEncryptUpdate,
.C_DecryptVerifyUpdate = crypto_ops.C_DecryptVerifyUpdate,
.C_GenerateKey = keymgmt.C_GenerateKey,
.C_GenerateKeyPair = keymgmt.C_GenerateKeyPair,
.C_WrapKey = keymgmt.C_WrapKey,
.C_UnwrapKey = keymgmt.C_UnwrapKey,
.C_DeriveKey = keymgmt.C_DeriveKey,
.C_SeedRandom = random.C_SeedRandom,
.C_GenerateRandom = random.C_GenerateRandom,
.C_GetFunctionStatus = general.C_GetFunctionStatus,
.C_CancelFunction = general.C_CancelFunction,
.C_WaitForSlotEvent = general.C_WaitForSlotEvent,
};

View File

@ -0,0 +1,15 @@
// ©AngelaMos | 2026
// test_all.zig
test {
_ = @import("crypto/pin.zig");
_ = @import("core/token.zig");
_ = @import("core/session.zig");
_ = @import("core/object_store.zig");
_ = @import("crypto/digest.zig");
_ = @import("crypto/mac.zig");
_ = @import("crypto/cipher.zig");
_ = @import("crypto/ecdsa.zig");
_ = @import("crypto/rsa.zig");
_ = @import("crypto/keystore.zig");
}

View File

@ -0,0 +1,12 @@
// ©AngelaMos | 2026
// util.zig
pub fn padded(comptime n: usize, comptime s: []const u8) [n]u8 {
if (s.len > n) @compileError("padded: source string longer than field width");
var out: [n]u8 = undefined;
var i: usize = 0;
while (i < n) : (i += 1) {
out[i] = if (i < s.len) s[i] else ' ';
}
return out;
}

View File

@ -0,0 +1,151 @@
// ©AngelaMos | 2026
// abi_test.zig
const std = @import("std");
const ck = @import("ck");
const p11c = @import("p11c");
const ptr = @sizeOf(usize);
test "scalar ABI widths match Cryptoki LP64" {
try std.testing.expectEqual(@as(usize, 1), @sizeOf(ck.CK_BYTE));
try std.testing.expectEqual(@as(usize, 1), @sizeOf(ck.CK_BBOOL));
try std.testing.expectEqual(@as(usize, 8), @sizeOf(ck.CK_ULONG));
try std.testing.expectEqual(@as(usize, 8), @sizeOf(ck.CK_RV));
try std.testing.expectEqual(@as(usize, 8), @sizeOf(ck.CK_SESSION_HANDLE));
try std.testing.expectEqual(@as(usize, 8), @sizeOf(ck.CK_OBJECT_HANDLE));
}
test "CK_VERSION is two packed bytes" {
try std.testing.expectEqual(@as(usize, 2), @sizeOf(ck.CK_VERSION));
try std.testing.expectEqual(@as(usize, 0), @offsetOf(ck.CK_VERSION, "major"));
try std.testing.expectEqual(@as(usize, 1), @offsetOf(ck.CK_VERSION, "minor"));
}
test "CK_ATTRIBUTE layout (type, pValue, ulValueLen)" {
try std.testing.expectEqual(@as(usize, 24), @sizeOf(ck.CK_ATTRIBUTE));
try std.testing.expectEqual(@as(usize, 0), @offsetOf(ck.CK_ATTRIBUTE, "type"));
try std.testing.expectEqual(@as(usize, 8), @offsetOf(ck.CK_ATTRIBUTE, "pValue"));
try std.testing.expectEqual(@as(usize, 16), @offsetOf(ck.CK_ATTRIBUTE, "ulValueLen"));
}
test "CK_MECHANISM layout" {
try std.testing.expectEqual(@as(usize, 24), @sizeOf(ck.CK_MECHANISM));
try std.testing.expectEqual(@as(usize, 0), @offsetOf(ck.CK_MECHANISM, "mechanism"));
try std.testing.expectEqual(@as(usize, 8), @offsetOf(ck.CK_MECHANISM, "pParameter"));
try std.testing.expectEqual(@as(usize, 16), @offsetOf(ck.CK_MECHANISM, "ulParameterLen"));
}
test "CK_INFO natural-alignment layout" {
try std.testing.expectEqual(@as(usize, 40), @offsetOf(ck.CK_INFO, "flags"));
try std.testing.expectEqual(@as(usize, 88), @sizeOf(ck.CK_INFO));
}
test "CK_TOKEN_INFO natural-alignment layout" {
try std.testing.expectEqual(@as(usize, 96), @offsetOf(ck.CK_TOKEN_INFO, "flags"));
try std.testing.expectEqual(@as(usize, 208), @sizeOf(ck.CK_TOKEN_INFO));
}
test "CK_FUNCTION_LIST is version + 68 pointers in canonical order" {
try std.testing.expectEqual(69 * ptr, @sizeOf(ck.CK_FUNCTION_LIST));
try std.testing.expectEqual(@as(usize, 0), @offsetOf(ck.CK_FUNCTION_LIST, "version"));
try std.testing.expectEqual(ptr, @offsetOf(ck.CK_FUNCTION_LIST, "C_Initialize"));
try std.testing.expectEqual(5 * ptr, @offsetOf(ck.CK_FUNCTION_LIST, "C_GetSlotList"));
try std.testing.expectEqual(68 * ptr, @offsetOf(ck.CK_FUNCTION_LIST, "C_WaitForSlotEvent"));
}
test "key return codes have canonical values" {
try std.testing.expectEqual(@as(ck.CK_RV, 0x00000000), ck.CKR_OK);
try std.testing.expectEqual(@as(ck.CK_RV, 0x00000054), ck.CKR_FUNCTION_NOT_SUPPORTED);
try std.testing.expectEqual(@as(ck.CK_RV, 0x00000150), ck.CKR_BUFFER_TOO_SMALL);
try std.testing.expectEqual(@as(ck.CK_RV, 0x00000190), ck.CKR_CRYPTOKI_NOT_INITIALIZED);
}
fn expectSameLayout(comptime A: type, comptime B: type) !void {
try std.testing.expectEqual(@sizeOf(A), @sizeOf(B));
try std.testing.expectEqual(@alignOf(A), @alignOf(B));
const fa = @typeInfo(A).@"struct".fields;
const fb = @typeInfo(B).@"struct".fields;
try std.testing.expectEqual(fa.len, fb.len);
inline for (fa) |f| {
try std.testing.expectEqual(@offsetOf(A, f.name), @offsetOf(B, f.name));
}
}
test "hand-coded structs match OASIS-translated layout byte-for-byte" {
try expectSameLayout(ck.CK_VERSION, p11c.CK_VERSION);
try expectSameLayout(ck.CK_INFO, p11c.CK_INFO);
try expectSameLayout(ck.CK_SLOT_INFO, p11c.CK_SLOT_INFO);
try expectSameLayout(ck.CK_TOKEN_INFO, p11c.CK_TOKEN_INFO);
try expectSameLayout(ck.CK_SESSION_INFO, p11c.CK_SESSION_INFO);
try expectSameLayout(ck.CK_MECHANISM_INFO, p11c.CK_MECHANISM_INFO);
try expectSameLayout(ck.CK_ATTRIBUTE, p11c.CK_ATTRIBUTE);
try expectSameLayout(ck.CK_MECHANISM, p11c.CK_MECHANISM);
try expectSameLayout(ck.CK_GCM_PARAMS, p11c.CK_GCM_PARAMS);
try expectSameLayout(ck.CK_DATE, p11c.CK_DATE);
try expectSameLayout(ck.CK_C_INITIALIZE_ARGS, p11c.CK_C_INITIALIZE_ARGS);
}
test "hand-coded CK_FUNCTION_LIST matches OASIS 68-entry order and size" {
try expectSameLayout(ck.CK_FUNCTION_LIST, p11c.CK_FUNCTION_LIST);
}
test "every hand-coded constant equals its OASIS value" {
@setEvalBranchQuota(20000);
comptime var checked: usize = 0;
inline for (@typeInfo(ck).@"struct".decls) |d| {
if (@hasDecl(p11c, d.name)) {
const value = @field(ck, d.name);
const T = @TypeOf(value);
if (T == type) continue;
const info = @typeInfo(T);
if (info != .int and info != .comptime_int) continue;
const ours: u64 = @intCast(value);
const theirs: u64 = @intCast(@field(p11c, d.name));
if (ours != theirs) {
std.debug.print("constant {s}: ck=0x{X} oasis=0x{X}\n", .{ d.name, ours, theirs });
return error.ConstantMismatch;
}
checked += 1;
}
}
try std.testing.expect(checked >= 100);
}
fn fnInfo(comptime FnPtr: type) std.builtin.Type.Fn {
const fn_ptr = @typeInfo(FnPtr).optional.child;
return @typeInfo(@typeInfo(fn_ptr).pointer.child).@"fn";
}
fn expectSameFnAbi(comptime name: []const u8, comptime A: type, comptime B: type) !void {
const fa = fnInfo(A);
const fb = fnInfo(B);
if (fa.params.len != fb.params.len) {
std.debug.print("fn {s}: param count ck={d} oasis={d}\n", .{ name, fa.params.len, fb.params.len });
return error.FnAbiMismatch;
}
if (@sizeOf(fa.return_type.?) != @sizeOf(fb.return_type.?)) {
std.debug.print("fn {s}: return-type size ck={d} oasis={d}\n", .{ name, @sizeOf(fa.return_type.?), @sizeOf(fb.return_type.?) });
return error.FnAbiMismatch;
}
inline for (0..@min(fa.params.len, fb.params.len)) |i| {
const ours = fa.params[i].type.?;
const theirs = fb.params[i].type.?;
if (@sizeOf(ours) != @sizeOf(theirs) or @alignOf(ours) != @alignOf(theirs)) {
std.debug.print("fn {s}: param {d} ck={d}/{d} oasis={d}/{d}\n", .{ name, i, @sizeOf(ours), @alignOf(ours), @sizeOf(theirs), @alignOf(theirs) });
return error.FnAbiMismatch;
}
}
}
test "every CK_FUNCTION_LIST entry matches the OASIS C ABI signature" {
try std.testing.expectEqual(@as(usize, 3), fnInfo(ck.CK_C_GetSlotList).params.len);
const cf = @typeInfo(ck.CK_FUNCTION_LIST).@"struct".fields;
const pf = @typeInfo(p11c.CK_FUNCTION_LIST).@"struct".fields;
inline for (cf, pf) |a, b| {
comptime std.debug.assert(std.mem.eql(u8, a.name, b.name));
if (comptime !std.mem.eql(u8, a.name, "version")) {
try expectSameFnAbi(a.name, a.type, b.type);
}
}
}

View File

@ -0,0 +1,34 @@
# Vendored PKCS#11 headers
`pkcs11.h`, `pkcs11t.h`, and `pkcs11f.h` are the **canonical OASIS headers**, fetched
**unmodified** from the published v2.40 errata-01 OASIS Standard:
```
https://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/include/pkcs11-v2.40/
```
They retain their original OASIS copyright notices and are **not** covered by this
project's license. They are vendored for one purpose only: a build-time ABI
cross-check (`zig build test`) that proves the hand-written Cryptoki ABI in
`src/ck.zig` matches the spec byte-for-byte.
## Integrity
SHA-256 of the vendored headers as fetched (verify with `sha256sum -c` after
re-fetching from the URL above):
```
8bb7aa1aeaa328b6a39913070d6f3d2bdeb9f2c92baf27f714fbb4cbefdf4054 pkcs11.h
5b58736b6d23f12b4d9492cd24b06b9d11056c3153afc4e89b1fe564749e71a2 pkcs11t.h
a85adad038bfc9dad9c71377f3ed3b049ba2ac9b3f37198a372f211d210c6057 pkcs11f.h
```
`shim.h` is the only file here authored by this project. It defines the five
caller-supplied macros the OASIS headers require (`CK_PTR`,
`CK_DECLARE_FUNCTION`, `CK_DECLARE_FUNCTION_POINTER`, `CK_CALLBACK_FUNCTION`,
`NULL_PTR`) and then includes `pkcs11.h`. `build.zig` runs `addTranslateC` on
`shim.h`; `tests/abi_test.zig` then asserts `@sizeOf`/`@offsetOf`/constant
equality between `src/ck.zig` and the translated headers.
The production `.so` does **not** depend on these headers — it ships the pure
hand-written `src/ck.zig`. These exist for regression-hardening of that file.

View File

@ -0,0 +1,265 @@
/* Copyright (c) OASIS Open 2016. All Rights Reserved./
* /Distributed under the terms of the OASIS IPR Policy,
* [http://www.oasis-open.org/policies-guidelines/ipr], AS-IS, WITHOUT ANY
* IMPLIED OR EXPRESS WARRANTY; there is no warranty of MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE or NONINFRINGEMENT of the rights of others.
*/
/* Latest version of the specification:
* http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.html
*/
#ifndef _PKCS11_H_
#define _PKCS11_H_ 1
#ifdef __cplusplus
extern "C" {
#endif
/* Before including this file (pkcs11.h) (or pkcs11t.h by
* itself), 5 platform-specific macros must be defined. These
* macros are described below, and typical definitions for them
* are also given. Be advised that these definitions can depend
* on both the platform and the compiler used (and possibly also
* on whether a Cryptoki library is linked statically or
* dynamically).
*
* In addition to defining these 5 macros, the packing convention
* for Cryptoki structures should be set. The Cryptoki
* convention on packing is that structures should be 1-byte
* aligned.
*
* If you're using Microsoft Developer Studio 5.0 to produce
* Win32 stuff, this might be done by using the following
* preprocessor directive before including pkcs11.h or pkcs11t.h:
*
* #pragma pack(push, cryptoki, 1)
*
* and using the following preprocessor directive after including
* pkcs11.h or pkcs11t.h:
*
* #pragma pack(pop, cryptoki)
*
* If you're using an earlier version of Microsoft Developer
* Studio to produce Win16 stuff, this might be done by using
* the following preprocessor directive before including
* pkcs11.h or pkcs11t.h:
*
* #pragma pack(1)
*
* In a UNIX environment, you're on your own for this. You might
* not need to do (or be able to do!) anything.
*
*
* Now for the macros:
*
*
* 1. CK_PTR: The indirection string for making a pointer to an
* object. It can be used like this:
*
* typedef CK_BYTE CK_PTR CK_BYTE_PTR;
*
* If you're using Microsoft Developer Studio 5.0 to produce
* Win32 stuff, it might be defined by:
*
* #define CK_PTR *
*
* If you're using an earlier version of Microsoft Developer
* Studio to produce Win16 stuff, it might be defined by:
*
* #define CK_PTR far *
*
* In a typical UNIX environment, it might be defined by:
*
* #define CK_PTR *
*
*
* 2. CK_DECLARE_FUNCTION(returnType, name): A macro which makes
* an importable Cryptoki library function declaration out of a
* return type and a function name. It should be used in the
* following fashion:
*
* extern CK_DECLARE_FUNCTION(CK_RV, C_Initialize)(
* CK_VOID_PTR pReserved
* );
*
* If you're using Microsoft Developer Studio 5.0 to declare a
* function in a Win32 Cryptoki .dll, it might be defined by:
*
* #define CK_DECLARE_FUNCTION(returnType, name) \
* returnType __declspec(dllimport) name
*
* If you're using an earlier version of Microsoft Developer
* Studio to declare a function in a Win16 Cryptoki .dll, it
* might be defined by:
*
* #define CK_DECLARE_FUNCTION(returnType, name) \
* returnType __export _far _pascal name
*
* In a UNIX environment, it might be defined by:
*
* #define CK_DECLARE_FUNCTION(returnType, name) \
* returnType name
*
*
* 3. CK_DECLARE_FUNCTION_POINTER(returnType, name): A macro
* which makes a Cryptoki API function pointer declaration or
* function pointer type declaration out of a return type and a
* function name. It should be used in the following fashion:
*
* // Define funcPtr to be a pointer to a Cryptoki API function
* // taking arguments args and returning CK_RV.
* CK_DECLARE_FUNCTION_POINTER(CK_RV, funcPtr)(args);
*
* or
*
* // Define funcPtrType to be the type of a pointer to a
* // Cryptoki API function taking arguments args and returning
* // CK_RV, and then define funcPtr to be a variable of type
* // funcPtrType.
* typedef CK_DECLARE_FUNCTION_POINTER(CK_RV, funcPtrType)(args);
* funcPtrType funcPtr;
*
* If you're using Microsoft Developer Studio 5.0 to access
* functions in a Win32 Cryptoki .dll, in might be defined by:
*
* #define CK_DECLARE_FUNCTION_POINTER(returnType, name) \
* returnType __declspec(dllimport) (* name)
*
* If you're using an earlier version of Microsoft Developer
* Studio to access functions in a Win16 Cryptoki .dll, it might
* be defined by:
*
* #define CK_DECLARE_FUNCTION_POINTER(returnType, name) \
* returnType __export _far _pascal (* name)
*
* In a UNIX environment, it might be defined by:
*
* #define CK_DECLARE_FUNCTION_POINTER(returnType, name) \
* returnType (* name)
*
*
* 4. CK_CALLBACK_FUNCTION(returnType, name): A macro which makes
* a function pointer type for an application callback out of
* a return type for the callback and a name for the callback.
* It should be used in the following fashion:
*
* CK_CALLBACK_FUNCTION(CK_RV, myCallback)(args);
*
* to declare a function pointer, myCallback, to a callback
* which takes arguments args and returns a CK_RV. It can also
* be used like this:
*
* typedef CK_CALLBACK_FUNCTION(CK_RV, myCallbackType)(args);
* myCallbackType myCallback;
*
* If you're using Microsoft Developer Studio 5.0 to do Win32
* Cryptoki development, it might be defined by:
*
* #define CK_CALLBACK_FUNCTION(returnType, name) \
* returnType (* name)
*
* If you're using an earlier version of Microsoft Developer
* Studio to do Win16 development, it might be defined by:
*
* #define CK_CALLBACK_FUNCTION(returnType, name) \
* returnType _far _pascal (* name)
*
* In a UNIX environment, it might be defined by:
*
* #define CK_CALLBACK_FUNCTION(returnType, name) \
* returnType (* name)
*
*
* 5. NULL_PTR: This macro is the value of a NULL pointer.
*
* In any ANSI/ISO C environment (and in many others as well),
* this should best be defined by
*
* #ifndef NULL_PTR
* #define NULL_PTR 0
* #endif
*/
/* All the various Cryptoki types and #define'd values are in the
* file pkcs11t.h.
*/
#include "pkcs11t.h"
#define __PASTE(x,y) x##y
/* ==============================================================
* Define the "extern" form of all the entry points.
* ==============================================================
*/
#define CK_NEED_ARG_LIST 1
#define CK_PKCS11_FUNCTION_INFO(name) \
extern CK_DECLARE_FUNCTION(CK_RV, name)
/* pkcs11f.h has all the information about the Cryptoki
* function prototypes.
*/
#include "pkcs11f.h"
#undef CK_NEED_ARG_LIST
#undef CK_PKCS11_FUNCTION_INFO
/* ==============================================================
* Define the typedef form of all the entry points. That is, for
* each Cryptoki function C_XXX, define a type CK_C_XXX which is
* a pointer to that kind of function.
* ==============================================================
*/
#define CK_NEED_ARG_LIST 1
#define CK_PKCS11_FUNCTION_INFO(name) \
typedef CK_DECLARE_FUNCTION_POINTER(CK_RV, __PASTE(CK_,name))
/* pkcs11f.h has all the information about the Cryptoki
* function prototypes.
*/
#include "pkcs11f.h"
#undef CK_NEED_ARG_LIST
#undef CK_PKCS11_FUNCTION_INFO
/* ==============================================================
* Define structed vector of entry points. A CK_FUNCTION_LIST
* contains a CK_VERSION indicating a library's Cryptoki version
* and then a whole slew of function pointers to the routines in
* the library. This type was declared, but not defined, in
* pkcs11t.h.
* ==============================================================
*/
#define CK_PKCS11_FUNCTION_INFO(name) \
__PASTE(CK_,name) name;
struct CK_FUNCTION_LIST {
CK_VERSION version; /* Cryptoki version */
/* Pile all the function pointers into the CK_FUNCTION_LIST. */
/* pkcs11f.h has all the information about the Cryptoki
* function prototypes.
*/
#include "pkcs11f.h"
};
#undef CK_PKCS11_FUNCTION_INFO
#undef __PASTE
#ifdef __cplusplus
}
#endif
#endif /* _PKCS11_H_ */

View File

@ -0,0 +1,939 @@
/* Copyright (c) OASIS Open 2016. All Rights Reserved./
* /Distributed under the terms of the OASIS IPR Policy,
* [http://www.oasis-open.org/policies-guidelines/ipr], AS-IS, WITHOUT ANY
* IMPLIED OR EXPRESS WARRANTY; there is no warranty of MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE or NONINFRINGEMENT of the rights of others.
*/
/* Latest version of the specification:
* http://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/pkcs11-base-v2.40.html
*/
/* This header file contains pretty much everything about all the
* Cryptoki function prototypes. Because this information is
* used for more than just declaring function prototypes, the
* order of the functions appearing herein is important, and
* should not be altered.
*/
/* General-purpose */
/* C_Initialize initializes the Cryptoki library. */
CK_PKCS11_FUNCTION_INFO(C_Initialize)
#ifdef CK_NEED_ARG_LIST
(
CK_VOID_PTR pInitArgs /* if this is not NULL_PTR, it gets
* cast to CK_C_INITIALIZE_ARGS_PTR
* and dereferenced
*/
);
#endif
/* C_Finalize indicates that an application is done with the
* Cryptoki library.
*/
CK_PKCS11_FUNCTION_INFO(C_Finalize)
#ifdef CK_NEED_ARG_LIST
(
CK_VOID_PTR pReserved /* reserved. Should be NULL_PTR */
);
#endif
/* C_GetInfo returns general information about Cryptoki. */
CK_PKCS11_FUNCTION_INFO(C_GetInfo)
#ifdef CK_NEED_ARG_LIST
(
CK_INFO_PTR pInfo /* location that receives information */
);
#endif
/* C_GetFunctionList returns the function list. */
CK_PKCS11_FUNCTION_INFO(C_GetFunctionList)
#ifdef CK_NEED_ARG_LIST
(
CK_FUNCTION_LIST_PTR_PTR ppFunctionList /* receives pointer to
* function list
*/
);
#endif
/* Slot and token management */
/* C_GetSlotList obtains a list of slots in the system. */
CK_PKCS11_FUNCTION_INFO(C_GetSlotList)
#ifdef CK_NEED_ARG_LIST
(
CK_BBOOL tokenPresent, /* only slots with tokens */
CK_SLOT_ID_PTR pSlotList, /* receives array of slot IDs */
CK_ULONG_PTR pulCount /* receives number of slots */
);
#endif
/* C_GetSlotInfo obtains information about a particular slot in
* the system.
*/
CK_PKCS11_FUNCTION_INFO(C_GetSlotInfo)
#ifdef CK_NEED_ARG_LIST
(
CK_SLOT_ID slotID, /* the ID of the slot */
CK_SLOT_INFO_PTR pInfo /* receives the slot information */
);
#endif
/* C_GetTokenInfo obtains information about a particular token
* in the system.
*/
CK_PKCS11_FUNCTION_INFO(C_GetTokenInfo)
#ifdef CK_NEED_ARG_LIST
(
CK_SLOT_ID slotID, /* ID of the token's slot */
CK_TOKEN_INFO_PTR pInfo /* receives the token information */
);
#endif
/* C_GetMechanismList obtains a list of mechanism types
* supported by a token.
*/
CK_PKCS11_FUNCTION_INFO(C_GetMechanismList)
#ifdef CK_NEED_ARG_LIST
(
CK_SLOT_ID slotID, /* ID of token's slot */
CK_MECHANISM_TYPE_PTR pMechanismList, /* gets mech. array */
CK_ULONG_PTR pulCount /* gets # of mechs. */
);
#endif
/* C_GetMechanismInfo obtains information about a particular
* mechanism possibly supported by a token.
*/
CK_PKCS11_FUNCTION_INFO(C_GetMechanismInfo)
#ifdef CK_NEED_ARG_LIST
(
CK_SLOT_ID slotID, /* ID of the token's slot */
CK_MECHANISM_TYPE type, /* type of mechanism */
CK_MECHANISM_INFO_PTR pInfo /* receives mechanism info */
);
#endif
/* C_InitToken initializes a token. */
CK_PKCS11_FUNCTION_INFO(C_InitToken)
#ifdef CK_NEED_ARG_LIST
(
CK_SLOT_ID slotID, /* ID of the token's slot */
CK_UTF8CHAR_PTR pPin, /* the SO's initial PIN */
CK_ULONG ulPinLen, /* length in bytes of the PIN */
CK_UTF8CHAR_PTR pLabel /* 32-byte token label (blank padded) */
);
#endif
/* C_InitPIN initializes the normal user's PIN. */
CK_PKCS11_FUNCTION_INFO(C_InitPIN)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_UTF8CHAR_PTR pPin, /* the normal user's PIN */
CK_ULONG ulPinLen /* length in bytes of the PIN */
);
#endif
/* C_SetPIN modifies the PIN of the user who is logged in. */
CK_PKCS11_FUNCTION_INFO(C_SetPIN)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_UTF8CHAR_PTR pOldPin, /* the old PIN */
CK_ULONG ulOldLen, /* length of the old PIN */
CK_UTF8CHAR_PTR pNewPin, /* the new PIN */
CK_ULONG ulNewLen /* length of the new PIN */
);
#endif
/* Session management */
/* C_OpenSession opens a session between an application and a
* token.
*/
CK_PKCS11_FUNCTION_INFO(C_OpenSession)
#ifdef CK_NEED_ARG_LIST
(
CK_SLOT_ID slotID, /* the slot's ID */
CK_FLAGS flags, /* from CK_SESSION_INFO */
CK_VOID_PTR pApplication, /* passed to callback */
CK_NOTIFY Notify, /* callback function */
CK_SESSION_HANDLE_PTR phSession /* gets session handle */
);
#endif
/* C_CloseSession closes a session between an application and a
* token.
*/
CK_PKCS11_FUNCTION_INFO(C_CloseSession)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession /* the session's handle */
);
#endif
/* C_CloseAllSessions closes all sessions with a token. */
CK_PKCS11_FUNCTION_INFO(C_CloseAllSessions)
#ifdef CK_NEED_ARG_LIST
(
CK_SLOT_ID slotID /* the token's slot */
);
#endif
/* C_GetSessionInfo obtains information about the session. */
CK_PKCS11_FUNCTION_INFO(C_GetSessionInfo)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_SESSION_INFO_PTR pInfo /* receives session info */
);
#endif
/* C_GetOperationState obtains the state of the cryptographic operation
* in a session.
*/
CK_PKCS11_FUNCTION_INFO(C_GetOperationState)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pOperationState, /* gets state */
CK_ULONG_PTR pulOperationStateLen /* gets state length */
);
#endif
/* C_SetOperationState restores the state of the cryptographic
* operation in a session.
*/
CK_PKCS11_FUNCTION_INFO(C_SetOperationState)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pOperationState, /* holds state */
CK_ULONG ulOperationStateLen, /* holds state length */
CK_OBJECT_HANDLE hEncryptionKey, /* en/decryption key */
CK_OBJECT_HANDLE hAuthenticationKey /* sign/verify key */
);
#endif
/* C_Login logs a user into a token. */
CK_PKCS11_FUNCTION_INFO(C_Login)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_USER_TYPE userType, /* the user type */
CK_UTF8CHAR_PTR pPin, /* the user's PIN */
CK_ULONG ulPinLen /* the length of the PIN */
);
#endif
/* C_Logout logs a user out from a token. */
CK_PKCS11_FUNCTION_INFO(C_Logout)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession /* the session's handle */
);
#endif
/* Object management */
/* C_CreateObject creates a new object. */
CK_PKCS11_FUNCTION_INFO(C_CreateObject)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_ATTRIBUTE_PTR pTemplate, /* the object's template */
CK_ULONG ulCount, /* attributes in template */
CK_OBJECT_HANDLE_PTR phObject /* gets new object's handle. */
);
#endif
/* C_CopyObject copies an object, creating a new object for the
* copy.
*/
CK_PKCS11_FUNCTION_INFO(C_CopyObject)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_OBJECT_HANDLE hObject, /* the object's handle */
CK_ATTRIBUTE_PTR pTemplate, /* template for new object */
CK_ULONG ulCount, /* attributes in template */
CK_OBJECT_HANDLE_PTR phNewObject /* receives handle of copy */
);
#endif
/* C_DestroyObject destroys an object. */
CK_PKCS11_FUNCTION_INFO(C_DestroyObject)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_OBJECT_HANDLE hObject /* the object's handle */
);
#endif
/* C_GetObjectSize gets the size of an object in bytes. */
CK_PKCS11_FUNCTION_INFO(C_GetObjectSize)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_OBJECT_HANDLE hObject, /* the object's handle */
CK_ULONG_PTR pulSize /* receives size of object */
);
#endif
/* C_GetAttributeValue obtains the value of one or more object
* attributes.
*/
CK_PKCS11_FUNCTION_INFO(C_GetAttributeValue)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_OBJECT_HANDLE hObject, /* the object's handle */
CK_ATTRIBUTE_PTR pTemplate, /* specifies attrs; gets vals */
CK_ULONG ulCount /* attributes in template */
);
#endif
/* C_SetAttributeValue modifies the value of one or more object
* attributes.
*/
CK_PKCS11_FUNCTION_INFO(C_SetAttributeValue)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_OBJECT_HANDLE hObject, /* the object's handle */
CK_ATTRIBUTE_PTR pTemplate, /* specifies attrs and values */
CK_ULONG ulCount /* attributes in template */
);
#endif
/* C_FindObjectsInit initializes a search for token and session
* objects that match a template.
*/
CK_PKCS11_FUNCTION_INFO(C_FindObjectsInit)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_ATTRIBUTE_PTR pTemplate, /* attribute values to match */
CK_ULONG ulCount /* attrs in search template */
);
#endif
/* C_FindObjects continues a search for token and session
* objects that match a template, obtaining additional object
* handles.
*/
CK_PKCS11_FUNCTION_INFO(C_FindObjects)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_OBJECT_HANDLE_PTR phObject, /* gets obj. handles */
CK_ULONG ulMaxObjectCount, /* max handles to get */
CK_ULONG_PTR pulObjectCount /* actual # returned */
);
#endif
/* C_FindObjectsFinal finishes a search for token and session
* objects.
*/
CK_PKCS11_FUNCTION_INFO(C_FindObjectsFinal)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession /* the session's handle */
);
#endif
/* Encryption and decryption */
/* C_EncryptInit initializes an encryption operation. */
CK_PKCS11_FUNCTION_INFO(C_EncryptInit)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_MECHANISM_PTR pMechanism, /* the encryption mechanism */
CK_OBJECT_HANDLE hKey /* handle of encryption key */
);
#endif
/* C_Encrypt encrypts single-part data. */
CK_PKCS11_FUNCTION_INFO(C_Encrypt)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pData, /* the plaintext data */
CK_ULONG ulDataLen, /* bytes of plaintext */
CK_BYTE_PTR pEncryptedData, /* gets ciphertext */
CK_ULONG_PTR pulEncryptedDataLen /* gets c-text size */
);
#endif
/* C_EncryptUpdate continues a multiple-part encryption
* operation.
*/
CK_PKCS11_FUNCTION_INFO(C_EncryptUpdate)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pPart, /* the plaintext data */
CK_ULONG ulPartLen, /* plaintext data len */
CK_BYTE_PTR pEncryptedPart, /* gets ciphertext */
CK_ULONG_PTR pulEncryptedPartLen /* gets c-text size */
);
#endif
/* C_EncryptFinal finishes a multiple-part encryption
* operation.
*/
CK_PKCS11_FUNCTION_INFO(C_EncryptFinal)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session handle */
CK_BYTE_PTR pLastEncryptedPart, /* last c-text */
CK_ULONG_PTR pulLastEncryptedPartLen /* gets last size */
);
#endif
/* C_DecryptInit initializes a decryption operation. */
CK_PKCS11_FUNCTION_INFO(C_DecryptInit)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_MECHANISM_PTR pMechanism, /* the decryption mechanism */
CK_OBJECT_HANDLE hKey /* handle of decryption key */
);
#endif
/* C_Decrypt decrypts encrypted data in a single part. */
CK_PKCS11_FUNCTION_INFO(C_Decrypt)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pEncryptedData, /* ciphertext */
CK_ULONG ulEncryptedDataLen, /* ciphertext length */
CK_BYTE_PTR pData, /* gets plaintext */
CK_ULONG_PTR pulDataLen /* gets p-text size */
);
#endif
/* C_DecryptUpdate continues a multiple-part decryption
* operation.
*/
CK_PKCS11_FUNCTION_INFO(C_DecryptUpdate)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pEncryptedPart, /* encrypted data */
CK_ULONG ulEncryptedPartLen, /* input length */
CK_BYTE_PTR pPart, /* gets plaintext */
CK_ULONG_PTR pulPartLen /* p-text size */
);
#endif
/* C_DecryptFinal finishes a multiple-part decryption
* operation.
*/
CK_PKCS11_FUNCTION_INFO(C_DecryptFinal)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pLastPart, /* gets plaintext */
CK_ULONG_PTR pulLastPartLen /* p-text size */
);
#endif
/* Message digesting */
/* C_DigestInit initializes a message-digesting operation. */
CK_PKCS11_FUNCTION_INFO(C_DigestInit)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_MECHANISM_PTR pMechanism /* the digesting mechanism */
);
#endif
/* C_Digest digests data in a single part. */
CK_PKCS11_FUNCTION_INFO(C_Digest)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pData, /* data to be digested */
CK_ULONG ulDataLen, /* bytes of data to digest */
CK_BYTE_PTR pDigest, /* gets the message digest */
CK_ULONG_PTR pulDigestLen /* gets digest length */
);
#endif
/* C_DigestUpdate continues a multiple-part message-digesting
* operation.
*/
CK_PKCS11_FUNCTION_INFO(C_DigestUpdate)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pPart, /* data to be digested */
CK_ULONG ulPartLen /* bytes of data to be digested */
);
#endif
/* C_DigestKey continues a multi-part message-digesting
* operation, by digesting the value of a secret key as part of
* the data already digested.
*/
CK_PKCS11_FUNCTION_INFO(C_DigestKey)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_OBJECT_HANDLE hKey /* secret key to digest */
);
#endif
/* C_DigestFinal finishes a multiple-part message-digesting
* operation.
*/
CK_PKCS11_FUNCTION_INFO(C_DigestFinal)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pDigest, /* gets the message digest */
CK_ULONG_PTR pulDigestLen /* gets byte count of digest */
);
#endif
/* Signing and MACing */
/* C_SignInit initializes a signature (private key encryption)
* operation, where the signature is (will be) an appendix to
* the data, and plaintext cannot be recovered from the
* signature.
*/
CK_PKCS11_FUNCTION_INFO(C_SignInit)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_MECHANISM_PTR pMechanism, /* the signature mechanism */
CK_OBJECT_HANDLE hKey /* handle of signature key */
);
#endif
/* C_Sign signs (encrypts with private key) data in a single
* part, where the signature is (will be) an appendix to the
* data, and plaintext cannot be recovered from the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_Sign)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pData, /* the data to sign */
CK_ULONG ulDataLen, /* count of bytes to sign */
CK_BYTE_PTR pSignature, /* gets the signature */
CK_ULONG_PTR pulSignatureLen /* gets signature length */
);
#endif
/* C_SignUpdate continues a multiple-part signature operation,
* where the signature is (will be) an appendix to the data,
* and plaintext cannot be recovered from the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_SignUpdate)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pPart, /* the data to sign */
CK_ULONG ulPartLen /* count of bytes to sign */
);
#endif
/* C_SignFinal finishes a multiple-part signature operation,
* returning the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_SignFinal)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pSignature, /* gets the signature */
CK_ULONG_PTR pulSignatureLen /* gets signature length */
);
#endif
/* C_SignRecoverInit initializes a signature operation, where
* the data can be recovered from the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_SignRecoverInit)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_MECHANISM_PTR pMechanism, /* the signature mechanism */
CK_OBJECT_HANDLE hKey /* handle of the signature key */
);
#endif
/* C_SignRecover signs data in a single operation, where the
* data can be recovered from the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_SignRecover)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pData, /* the data to sign */
CK_ULONG ulDataLen, /* count of bytes to sign */
CK_BYTE_PTR pSignature, /* gets the signature */
CK_ULONG_PTR pulSignatureLen /* gets signature length */
);
#endif
/* Verifying signatures and MACs */
/* C_VerifyInit initializes a verification operation, where the
* signature is an appendix to the data, and plaintext cannot
* cannot be recovered from the signature (e.g. DSA).
*/
CK_PKCS11_FUNCTION_INFO(C_VerifyInit)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_MECHANISM_PTR pMechanism, /* the verification mechanism */
CK_OBJECT_HANDLE hKey /* verification key */
);
#endif
/* C_Verify verifies a signature in a single-part operation,
* where the signature is an appendix to the data, and plaintext
* cannot be recovered from the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_Verify)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pData, /* signed data */
CK_ULONG ulDataLen, /* length of signed data */
CK_BYTE_PTR pSignature, /* signature */
CK_ULONG ulSignatureLen /* signature length*/
);
#endif
/* C_VerifyUpdate continues a multiple-part verification
* operation, where the signature is an appendix to the data,
* and plaintext cannot be recovered from the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_VerifyUpdate)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pPart, /* signed data */
CK_ULONG ulPartLen /* length of signed data */
);
#endif
/* C_VerifyFinal finishes a multiple-part verification
* operation, checking the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_VerifyFinal)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pSignature, /* signature to verify */
CK_ULONG ulSignatureLen /* signature length */
);
#endif
/* C_VerifyRecoverInit initializes a signature verification
* operation, where the data is recovered from the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_VerifyRecoverInit)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_MECHANISM_PTR pMechanism, /* the verification mechanism */
CK_OBJECT_HANDLE hKey /* verification key */
);
#endif
/* C_VerifyRecover verifies a signature in a single-part
* operation, where the data is recovered from the signature.
*/
CK_PKCS11_FUNCTION_INFO(C_VerifyRecover)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pSignature, /* signature to verify */
CK_ULONG ulSignatureLen, /* signature length */
CK_BYTE_PTR pData, /* gets signed data */
CK_ULONG_PTR pulDataLen /* gets signed data len */
);
#endif
/* Dual-function cryptographic operations */
/* C_DigestEncryptUpdate continues a multiple-part digesting
* and encryption operation.
*/
CK_PKCS11_FUNCTION_INFO(C_DigestEncryptUpdate)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pPart, /* the plaintext data */
CK_ULONG ulPartLen, /* plaintext length */
CK_BYTE_PTR pEncryptedPart, /* gets ciphertext */
CK_ULONG_PTR pulEncryptedPartLen /* gets c-text length */
);
#endif
/* C_DecryptDigestUpdate continues a multiple-part decryption and
* digesting operation.
*/
CK_PKCS11_FUNCTION_INFO(C_DecryptDigestUpdate)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pEncryptedPart, /* ciphertext */
CK_ULONG ulEncryptedPartLen, /* ciphertext length */
CK_BYTE_PTR pPart, /* gets plaintext */
CK_ULONG_PTR pulPartLen /* gets plaintext len */
);
#endif
/* C_SignEncryptUpdate continues a multiple-part signing and
* encryption operation.
*/
CK_PKCS11_FUNCTION_INFO(C_SignEncryptUpdate)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pPart, /* the plaintext data */
CK_ULONG ulPartLen, /* plaintext length */
CK_BYTE_PTR pEncryptedPart, /* gets ciphertext */
CK_ULONG_PTR pulEncryptedPartLen /* gets c-text length */
);
#endif
/* C_DecryptVerifyUpdate continues a multiple-part decryption and
* verify operation.
*/
CK_PKCS11_FUNCTION_INFO(C_DecryptVerifyUpdate)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_BYTE_PTR pEncryptedPart, /* ciphertext */
CK_ULONG ulEncryptedPartLen, /* ciphertext length */
CK_BYTE_PTR pPart, /* gets plaintext */
CK_ULONG_PTR pulPartLen /* gets p-text length */
);
#endif
/* Key management */
/* C_GenerateKey generates a secret key, creating a new key
* object.
*/
CK_PKCS11_FUNCTION_INFO(C_GenerateKey)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_MECHANISM_PTR pMechanism, /* key generation mech. */
CK_ATTRIBUTE_PTR pTemplate, /* template for new key */
CK_ULONG ulCount, /* # of attrs in template */
CK_OBJECT_HANDLE_PTR phKey /* gets handle of new key */
);
#endif
/* C_GenerateKeyPair generates a public-key/private-key pair,
* creating new key objects.
*/
CK_PKCS11_FUNCTION_INFO(C_GenerateKeyPair)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session handle */
CK_MECHANISM_PTR pMechanism, /* key-gen mech. */
CK_ATTRIBUTE_PTR pPublicKeyTemplate, /* template for pub. key */
CK_ULONG ulPublicKeyAttributeCount, /* # pub. attrs. */
CK_ATTRIBUTE_PTR pPrivateKeyTemplate, /* template for priv. key */
CK_ULONG ulPrivateKeyAttributeCount, /* # priv. attrs. */
CK_OBJECT_HANDLE_PTR phPublicKey, /* gets pub. key handle */
CK_OBJECT_HANDLE_PTR phPrivateKey /* gets priv. key handle */
);
#endif
/* C_WrapKey wraps (i.e., encrypts) a key. */
CK_PKCS11_FUNCTION_INFO(C_WrapKey)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_MECHANISM_PTR pMechanism, /* the wrapping mechanism */
CK_OBJECT_HANDLE hWrappingKey, /* wrapping key */
CK_OBJECT_HANDLE hKey, /* key to be wrapped */
CK_BYTE_PTR pWrappedKey, /* gets wrapped key */
CK_ULONG_PTR pulWrappedKeyLen /* gets wrapped key size */
);
#endif
/* C_UnwrapKey unwraps (decrypts) a wrapped key, creating a new
* key object.
*/
CK_PKCS11_FUNCTION_INFO(C_UnwrapKey)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_MECHANISM_PTR pMechanism, /* unwrapping mech. */
CK_OBJECT_HANDLE hUnwrappingKey, /* unwrapping key */
CK_BYTE_PTR pWrappedKey, /* the wrapped key */
CK_ULONG ulWrappedKeyLen, /* wrapped key len */
CK_ATTRIBUTE_PTR pTemplate, /* new key template */
CK_ULONG ulAttributeCount, /* template length */
CK_OBJECT_HANDLE_PTR phKey /* gets new handle */
);
#endif
/* C_DeriveKey derives a key from a base key, creating a new key
* object.
*/
CK_PKCS11_FUNCTION_INFO(C_DeriveKey)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* session's handle */
CK_MECHANISM_PTR pMechanism, /* key deriv. mech. */
CK_OBJECT_HANDLE hBaseKey, /* base key */
CK_ATTRIBUTE_PTR pTemplate, /* new key template */
CK_ULONG ulAttributeCount, /* template length */
CK_OBJECT_HANDLE_PTR phKey /* gets new handle */
);
#endif
/* Random number generation */
/* C_SeedRandom mixes additional seed material into the token's
* random number generator.
*/
CK_PKCS11_FUNCTION_INFO(C_SeedRandom)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR pSeed, /* the seed material */
CK_ULONG ulSeedLen /* length of seed material */
);
#endif
/* C_GenerateRandom generates random data. */
CK_PKCS11_FUNCTION_INFO(C_GenerateRandom)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession, /* the session's handle */
CK_BYTE_PTR RandomData, /* receives the random data */
CK_ULONG ulRandomLen /* # of bytes to generate */
);
#endif
/* Parallel function management */
/* C_GetFunctionStatus is a legacy function; it obtains an
* updated status of a function running in parallel with an
* application.
*/
CK_PKCS11_FUNCTION_INFO(C_GetFunctionStatus)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession /* the session's handle */
);
#endif
/* C_CancelFunction is a legacy function; it cancels a function
* running in parallel.
*/
CK_PKCS11_FUNCTION_INFO(C_CancelFunction)
#ifdef CK_NEED_ARG_LIST
(
CK_SESSION_HANDLE hSession /* the session's handle */
);
#endif
/* C_WaitForSlotEvent waits for a slot event (token insertion,
* removal, etc.) to occur.
*/
CK_PKCS11_FUNCTION_INFO(C_WaitForSlotEvent)
#ifdef CK_NEED_ARG_LIST
(
CK_FLAGS flags, /* blocking/nonblocking flag */
CK_SLOT_ID_PTR pSlot, /* location that receives the slot ID */
CK_VOID_PTR pRserved /* reserved. Should be NULL_PTR */
);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
// ©AngelaMos | 2026
// shim.h
#ifndef ANGELAMOS_PKCS11_SHIM_H
#define ANGELAMOS_PKCS11_SHIM_H
#define CK_PTR *
#define CK_DECLARE_FUNCTION(returnType, name) returnType name
#define CK_DECLARE_FUNCTION_POINTER(returnType, name) returnType (*name)
#define CK_CALLBACK_FUNCTION(returnType, name) returnType (*name)
#ifndef NULL_PTR
#define NULL_PTR 0
#endif
#include "pkcs11.h"
#endif