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:
parent
9e7246e417
commit
ccb1b40a15
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# ©AngelaMos | 2026
|
||||
# .dockerignore
|
||||
.zig-cache
|
||||
zig-out
|
||||
docs
|
||||
.git
|
||||
*.tmp
|
||||
|
|
@ -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
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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/>.
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
```
|
||||
██╗ ██╗███████╗███╗ ███╗ ███████╗███╗ ███╗██╗ ██╗██╗ █████╗ ████████╗ ██████╗ ██████╗
|
||||
██║ ██║██╔════╝████╗ ████║ ██╔════╝████╗ ████║██║ ██║██║ ██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗
|
||||
███████║███████╗██╔████╔██║ █████╗ ██╔████╔██║██║ ██║██║ ███████║ ██║ ██║ ██║██████╔╝
|
||||
██╔══██║╚════██║██║╚██╔╝██║ ██╔══╝ ██║╚██╔╝██║██║ ██║██║ ██╔══██║ ██║ ██║ ██║██╔══██╗
|
||||
██║ ██║███████║██║ ╚═╝ ██║ ███████╗██║ ╚═╝ ██║╚██████╔╝███████╗██║ ██║ ██║ ╚██████╔╝██║ ██║
|
||||
╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
```
|
||||
|
||||
[](https://github.com/CarterPerez-dev/Cybersecurity-Projects/tree/main/PROJECTS/advanced/hsm-emulator)
|
||||
[](https://ziglang.org)
|
||||
[](https://docs.oasis-open.org/pkcs11/pkcs11-base/v2.40/errata01/os/pkcs11-base-v2.40-errata01-os.html)
|
||||
[](https://github.com/OpenSC/OpenSC)
|
||||
[](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)
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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",
|
||||
},
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 "$@"
|
||||
|
|
@ -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"
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
/* ©AngelaMos | 2026 */
|
||||
/* pkcs11.map */
|
||||
PKCS11_2_40 {
|
||||
global:
|
||||
C_GetFunctionList;
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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 });
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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_ */
|
||||
|
||||
|
|
@ -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
|
|
@ -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
|
||||
Loading…
Reference in New Issue