/* AF_ALG crypto implementation. Copyright 2010 Red Hat, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Red Hat author: Miloslav Trmač */ #include #include #include #include #include #include #include #include #include #include "internal.h" /* This is in , but not exposed by it to user-space. */ #define AF_ALG 38 /* Algorithm sockets */ #define SOL_ALG 279 /* Helpers */ static CK_RV ckr_errno (void) { /* FIXME: Something better */ return CKR_GENERAL_ERROR; } /* Digest handling */ /* Semantically, md_size_ptr should be "size_t *", but values will fit into "int *" just as fine. */ static CK_RV digest_params_from_mech (CK_MECHANISM_TYPE mech, const struct sockaddr_alg **sa_ptr, int *md_size_ptr) { const struct sockaddr_alg *sa; int md_size; switch (mech) { #define E(MECH, NAME, SIZE) \ case CKM_##MECH: \ { \ static const struct sockaddr_alg addr = \ { .salg_family = AF_ALG, .salg_type = "hash", .salg_name = (NAME) }; \ \ sa = &addr; \ md_size = (SIZE); \ break; \ } \ E (MD5, "md5", 16); E (SHA_1, "sha1", 20); E (SHA224, "sha224", 28); E (SHA256, "sha256", 32); E (SHA384, "sha384", 48); E (SHA512, "sha512", 64); #undef E default: g_return_val_if_reached (CKR_MECHANISM_INVALID); } *sa_ptr = sa; *md_size_ptr = md_size; return CKR_OK; } struct ncr_digest_session { int fd; int md_size; /* size_t semantically */ /* Debugging only */ enum { NDS_NEW, NDS_INITIALIZED, NDS_UPDATED, NDS_FINISHED } state; }; CK_RV ncr_digest_alloc (struct ncr_digest_session **sess, CK_MECHANISM_TYPE mech) { struct ncr_digest_session *s; const struct sockaddr_alg *sa; int top_fd; CK_RV res; g_return_val_if_fail (sess != NULL, CKR_ARGUMENTS_BAD); s = malloc (sizeof (*s)); if (s == NULL) return CKR_HOST_MEMORY; res = digest_params_from_mech (mech, &sa, &s->md_size); if (res != CKR_OK) goto err_s; top_fd = socket (AF_ALG, SOCK_SEQPACKET, 0); if (top_fd == -1) { res = ckr_errno (); goto err_s; } if (bind (top_fd, (struct sockaddr *)sa, sizeof (*sa)) != 0) { res = ckr_errno (); goto err_top_fd; } s->fd = accept (top_fd, NULL, 0); if (s->fd == -1) { res = ckr_errno (); goto err_top_fd; } (void)close (top_fd); s->state = NDS_NEW; *sess = s; return CKR_OK; err_top_fd: (void)close (top_fd); err_s: free (s); return res; } CK_RV ncr_digest_free (struct ncr_digest_session *sess) { g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); (void)close (sess->fd); free (sess); return CKR_OK; } CK_RV ncr_digest_clone (struct ncr_digest_session **clone, struct ncr_digest_session *sess) { struct ncr_digest_session *c; g_return_val_if_fail (clone != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NDS_INITIALIZED || sess->state == NDS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); c = malloc (sizeof (*c)); if (c == NULL) return CKR_HOST_MEMORY; c->fd = accept (sess->fd, NULL, 0); if (c->fd == -1) { free (c); return ckr_errno (); } c->state = sess->state; c->md_size = sess->md_size; *clone = c; return CKR_OK; } CK_RV ncr_digest_init (struct ncr_digest_session *sess) { g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NDS_NEW || sess->state == NDS_FINISHED, CKR_OPERATION_ACTIVE); /* Nothing to do. */ sess->state = NDS_INITIALIZED; return CKR_OK; } CK_RV ncr_digest_update (struct ncr_digest_session *sess, const void *data, size_t size) { ssize_t res; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (data != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (size <= SSIZE_MAX, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess->state == NDS_INITIALIZED || sess->state == NDS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); res = sendto (sess->fd, data, size, MSG_MORE, NULL, 0); if (res == -1) return ckr_errno (); if (res != (ssize_t)size) /* "size" overflow verified above */ return CKR_GENERAL_ERROR; /* What? */ sess->state = NDS_UPDATED; return CKR_OK; } CK_RV ncr_digest_final (struct ncr_digest_session *sess, void *dest, size_t *size_ptr) { ssize_t res; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NDS_INITIALIZED || sess->state == NDS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); g_return_val_if_fail (size_ptr != NULL, CKR_ARGUMENTS_BAD); if (dest == NULL) { *size_ptr = sess->md_size; return CKR_OK; } if (*size_ptr < (size_t)sess->md_size) { *size_ptr = sess->md_size; return CKR_BUFFER_TOO_SMALL; } *size_ptr = sess->md_size; g_return_val_if_fail (dest != NULL, CKR_ARGUMENTS_BAD); res = write (sess->fd, "", 0); if (res != -1) res = read (sess->fd, dest, sess->md_size); sess->state = NDS_FINISHED; if (res == sess->md_size) return CKR_OK; if (res == -1) return ckr_errno (); return CKR_GENERAL_ERROR; /* What? */ } CK_RV ncr_digest (struct ncr_digest_session *sess, void *dest, size_t *dest_size_ptr, const void *data, size_t data_size) { ssize_t res; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NDS_INITIALIZED, CKR_OPERATION_NOT_INITIALIZED); g_return_val_if_fail (dest_size_ptr != NULL, CKR_ARGUMENTS_BAD); if (dest == NULL) { *dest_size_ptr = sess->md_size; return CKR_OK; } if (*dest_size_ptr < (size_t)sess->md_size) { *dest_size_ptr = sess->md_size; return CKR_BUFFER_TOO_SMALL; } *dest_size_ptr = sess->md_size; g_return_val_if_fail (data != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (data_size <= SSIZE_MAX, CKR_ARGUMENTS_BAD); sess->state = NDS_FINISHED; res = write (sess->fd, data, data_size); if (res == (ssize_t)data_size) /* "data_size" overflow verified above */ { res = read (sess->fd, dest, sess->md_size); if (res == sess->md_size) return CKR_OK; } if (res == -1) return ckr_errno (); return CKR_GENERAL_ERROR; /* What? */ } CK_RV ncr_digest_standalone (CK_MECHANISM_TYPE mech, void *dest, size_t *dest_size_ptr, const void *data, size_t data_size) { const struct sockaddr_alg *sa; int top_fd, fd, md_size; ssize_t res; CK_RV ret; g_return_val_if_fail (dest_size_ptr != NULL, CKR_ARGUMENTS_BAD); ret = digest_params_from_mech (mech, &sa, &md_size); if (ret != CKR_OK) return ret; if (dest == NULL) { *dest_size_ptr = md_size; return CKR_OK; } if (*dest_size_ptr < (size_t)md_size) { *dest_size_ptr = md_size; return CKR_BUFFER_TOO_SMALL; } *dest_size_ptr = md_size; g_return_val_if_fail (data != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (data_size <= SSIZE_MAX, CKR_ARGUMENTS_BAD); top_fd = socket (AF_ALG, SOCK_SEQPACKET, 0); if (top_fd == -1) return ckr_errno (); if (bind (top_fd, (struct sockaddr *)sa, sizeof (*sa)) != 0) goto err_top_fd; fd = accept (top_fd, NULL, 0); if (fd == -1) goto err_top_fd; (void)close (top_fd); res = write (fd, data, data_size); if (res == (ssize_t)data_size) /* "data_size" overflow verified above */ { res = read (fd, dest, md_size); if (res == md_size) { ret = CKR_OK; goto end; } } if (res == -1) ret = ckr_errno (); else ret = CKR_GENERAL_ERROR; /* What? */ end: close (fd); return ret; err_top_fd: ret = ckr_errno (); (void)close (top_fd); return ret; } /* Symmetric signature handling */ #define MAX_HMAC_SIZE 64 /* In the kernel, digests and HMAC are both .salg_type = "hash", but we need to treat them differently: For digest sessions, we only need to keep the slave file descriptor, but for HMAC we need the parent to be able to change the key. */ struct ncr_symm_signature_session { int parent_fd, child_fd; const struct sockaddr_alg *sa; int md_size; /* size_t semantically */ bool signing; /* Debugging only */ enum { NSSS_NEW, NSSS_INITIALIZED, NSSS_UPDATED, NSSS_FINISHED } state; }; static CK_RV symm_signature_params_from_mech (struct ncr_symm_signature_session *sess, CK_MECHANISM_TYPE mech) { const struct sockaddr_alg *sa; int md_size; switch (mech) { #define E(MECH, NAME, SIZE) \ case CKM_##MECH: \ { \ static const struct sockaddr_alg addr = \ { .salg_family = AF_ALG, .salg_type = "hash", .salg_name = (NAME) }; \ G_STATIC_ASSERT ((SIZE) <= MAX_HMAC_SIZE); \ \ sa = &addr; \ md_size = (SIZE); \ break; \ } E (MD5_HMAC, "hmac(md5)", 16); E (SHA_1_HMAC, "hmac(sha1)", 20); E (SHA224_HMAC, "hmac(sha224)", 28); E (SHA256_HMAC, "hmac(sha256)", 32); E (SHA384_HMAC, "hmac(sha384)", 48); E (SHA512_HMAC, "hmac(sha512)", 64); #undef E default: g_return_val_if_reached (CKR_MECHANISM_INVALID); } sess->sa = sa; sess->md_size = md_size; return CKR_OK; } CK_RV ncr_symm_signature_alloc (struct ncr_symm_signature_session **sess, CK_MECHANISM_TYPE mech) { struct ncr_symm_signature_session *s; CK_RV res; g_return_val_if_fail (sess != NULL, CKR_ARGUMENTS_BAD); s = malloc (sizeof (*s)); if (s == NULL) return CKR_HOST_MEMORY; res = symm_signature_params_from_mech (s, mech); if (res != CKR_OK) goto err_s; s->parent_fd = socket (AF_ALG, SOCK_SEQPACKET, 0); if (s->parent_fd == -1) { res = ckr_errno (); goto err_s; } if (bind (s->parent_fd, (struct sockaddr *)s->sa, sizeof (*s->sa)) != 0) { res = ckr_errno (); goto err_parent_fd; } s->child_fd = -1; s->state = NSSS_NEW; *sess = s; return CKR_OK; err_parent_fd: (void)close (s->parent_fd); err_s: free (s); return res; } CK_RV ncr_symm_signature_free (struct ncr_symm_signature_session *sess) { g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); if (sess->child_fd != -1) (void)close (sess->child_fd); (void)close (sess->parent_fd); free (sess); return CKR_OK; } CK_RV ncr_symm_signature_clone (struct ncr_symm_signature_session **clone, struct ncr_symm_signature_session *sess) { struct ncr_symm_signature_session *c; int new_sess_parent_fd; CK_RV res; g_return_val_if_fail (clone != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSSS_INITIALIZED || sess->state == NSSS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); c = malloc (sizeof (*c)); if (c == NULL) return CKR_HOST_MEMORY; /* This is a little complicated: parent_fd holds the "transform", which is parametrized by the key. A mere accept () on child_fd would create two sockets sharing a parent_fd, and reinitializing a session on one with a different key would change the underlying key of the other. Therefore, create an entirely new parent_fd for _both_ clones. The "old" parent_fd will be implicitly referred to by child_fd of both clones, but any session reinitialization will use the new parent_fd values. */ new_sess_parent_fd = socket (AF_ALG, SOCK_SEQPACKET, 0); if (new_sess_parent_fd == -1) { res = ckr_errno (); goto err_c; } if (bind (new_sess_parent_fd, (struct sockaddr *)sess->sa, sizeof (*sess->sa)) != 0) { res = ckr_errno (); goto err_new_sess_parent_fd; } c->parent_fd = socket (AF_ALG, SOCK_SEQPACKET, 0); if (c->parent_fd == -1) { res = ckr_errno (); goto err_new_sess_parent_fd; } if (bind (c->parent_fd, (struct sockaddr *)sess->sa, sizeof (*sess->sa)) != 0) { res = ckr_errno (); goto err_c_parent_fd; } c->child_fd = accept (sess->child_fd, NULL, 0); if (c->child_fd == -1) { res = ckr_errno (); goto err_c_parent_fd; } (void)close (sess->parent_fd); sess->parent_fd = new_sess_parent_fd; c->state = sess->state; c->sa = sess->sa; c->md_size = sess->md_size; c->signing = sess->signing; *clone = c; return CKR_OK; err_c_parent_fd: (void)close (c->parent_fd); err_new_sess_parent_fd: (void)close (new_sess_parent_fd); err_c: free (c); return res; } static CK_RV symm_signature_init (struct ncr_symm_signature_session *sess, bool sign, struct ncr_symm_key *key) { g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSSS_NEW || sess->state == NSSS_FINISHED, CKR_OPERATION_ACTIVE); g_return_val_if_fail (key != NULL, CKR_KEY_HANDLE_INVALID); /* We need key->size to fit into socklen_t. There is no SOCKLEN_MAX, but it is at least INT32_MAX, which is plenty. */ g_return_val_if_fail (key->size <= INT32_MAX, CKR_KEY_SIZE_RANGE); /* This is not assured, but holds for supported mechanisms. */ g_return_val_if_fail (key->type == CKK_GENERIC_SECRET, CKR_KEY_TYPE_INCONSISTENT); if (setsockopt (sess->parent_fd, SOL_ALG, ALG_SET_KEY, key->value, key->size) != 0) return ckr_errno (); sess->child_fd = accept (sess->parent_fd, NULL, 0); if (sess->child_fd == -1) return ckr_errno (); sess->signing = sign; sess->state = NSSS_INITIALIZED; return CKR_OK; } static CK_RV symm_signature_update (struct ncr_symm_signature_session *sess, bool sign, const void *data, size_t size) { ssize_t res; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (data != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (size <= SSIZE_MAX, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess->state == NSSS_INITIALIZED || sess->state == NSSS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); g_return_val_if_fail (sess->signing == sign, CKR_OPERATION_NOT_INITIALIZED); res = sendto (sess->child_fd, data, size, MSG_MORE, NULL, 0); if (res == -1) return ckr_errno (); if (res != (ssize_t)size) /* "size" overflow verified above */ return CKR_GENERAL_ERROR; /* What? */ sess->state = NSSS_UPDATED; return CKR_OK; } CK_RV ncr_symm_signature_sign_init (struct ncr_symm_signature_session *sess, struct ncr_symm_key *key) { return symm_signature_init (sess, true, key); } CK_RV ncr_symm_signature_sign_update (struct ncr_symm_signature_session *sess, const void *data, size_t size) { return symm_signature_update (sess, true, data, size); } CK_RV ncr_symm_signature_sign_final (struct ncr_symm_signature_session *sess, void *dest, size_t *size_ptr) { int res; CK_RV ret; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSSS_INITIALIZED || sess->state == NSSS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); g_return_val_if_fail (size_ptr != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess->signing == true, CKR_OPERATION_NOT_INITIALIZED); if (dest == NULL) { *size_ptr = sess->md_size; return CKR_OK; } if (*size_ptr < (size_t)sess->md_size) { *size_ptr = sess->md_size; return CKR_BUFFER_TOO_SMALL; } *size_ptr = sess->md_size; g_return_val_if_fail (dest != NULL, CKR_ARGUMENTS_BAD); res = write (sess->child_fd, "", 0); if (res != -1) res = read (sess->child_fd, dest, sess->md_size); if (res == sess->md_size) ret = CKR_OK; else if (res == -1) ret = ckr_errno (); else ret = CKR_GENERAL_ERROR; /* What? */ (void)close (sess->child_fd); sess->child_fd = -1; sess->state = NSSS_FINISHED; return ret; } CK_RV ncr_symm_signature_sign (struct ncr_symm_signature_session *sess, void *dest, size_t *dest_size_ptr, const void *data, size_t data_size) { int res; CK_RV ret; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSSS_INITIALIZED, CKR_OPERATION_NOT_INITIALIZED); g_return_val_if_fail (dest_size_ptr != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess->signing == true, CKR_OPERATION_NOT_INITIALIZED); if (dest == NULL) { *dest_size_ptr = sess->md_size; return CKR_OK; } if (*dest_size_ptr < (size_t)sess->md_size) { *dest_size_ptr = sess->md_size; return CKR_BUFFER_TOO_SMALL; } *dest_size_ptr = sess->md_size; g_return_val_if_fail (data != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (data_size <= SSIZE_MAX, CKR_ARGUMENTS_BAD); res = write (sess->child_fd, data, data_size); if (res == (ssize_t)data_size) /* "data_size" overflow verified above */ { res = read (sess->child_fd, dest, sess->md_size); if (res == sess->md_size) { ret = CKR_OK; goto end; } } if (res == -1) ret = ckr_errno (); else ret = CKR_GENERAL_ERROR; /* What? */ end: (void)close (sess->child_fd); sess->child_fd = -1; sess->state = NSSS_FINISHED; return ret; } CK_RV ncr_symm_signature_verify_init (struct ncr_symm_signature_session *sess, struct ncr_symm_key *key) { return symm_signature_init (sess, false, key); } CK_RV ncr_symm_signature_verify_update (struct ncr_symm_signature_session *sess, const void *data, size_t size) { return symm_signature_update (sess, false, data, size); } CK_RV ncr_symm_signature_verify_final (struct ncr_symm_signature_session *sess, const void *signature, size_t size) { uint8_t buf[MAX_HMAC_SIZE]; int res; CK_RV ret; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSSS_INITIALIZED || sess->state == NSSS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); g_return_val_if_fail (signature != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess->signing == false, CKR_OPERATION_NOT_INITIALIZED); res = write (sess->child_fd, "", 0); if (res != -1) res = read (sess->child_fd, buf, sess->md_size); if (res == sess->md_size) { if (size != (size_t)sess->md_size) ret = CKR_SIGNATURE_LEN_RANGE; else if (memcmp (signature, buf, size) != 0) ret = CKR_SIGNATURE_INVALID; else ret = CKR_OK; } else if (res == -1) ret = ckr_errno (); else ret = CKR_GENERAL_ERROR; /* What? */ (void)close (sess->child_fd); sess->child_fd = -1; sess->state = NSSS_FINISHED; return ret; } CK_RV ncr_symm_signature_verify (struct ncr_symm_signature_session *sess, const void *signature, size_t signature_size, const void *data, size_t data_size) { uint8_t buf[MAX_HMAC_SIZE]; int res; CK_RV ret; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSSS_INITIALIZED, CKR_OPERATION_NOT_INITIALIZED); g_return_val_if_fail (data != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (data_size <= SSIZE_MAX, CKR_ARGUMENTS_BAD); g_return_val_if_fail (signature != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess->signing == false, CKR_OPERATION_NOT_INITIALIZED); res = write (sess->child_fd, data, data_size); if (res == (ssize_t)data_size) /* "data_size" overflow verified above */ { res = read (sess->child_fd, buf, sess->md_size); if (res == sess->md_size) { if (signature_size != (size_t)sess->md_size) ret = CKR_SIGNATURE_LEN_RANGE; else if (memcmp (signature, buf, signature_size) != 0) ret = CKR_SIGNATURE_INVALID; else ret = CKR_OK; goto end; } } if (res == -1) ret = ckr_errno (); else ret = CKR_GENERAL_ERROR; /* What? */ end: (void)close (sess->child_fd); sess->child_fd = -1; sess->state = NSSS_FINISHED; return ret; } /* Symmetric encryption */ static CK_RV cipher_params_from_mech (CK_MECHANISM_TYPE mech, const struct sockaddr_alg **sa_ptr) { const struct sockaddr_alg *sa; switch (mech) { #define E(MECH, NAME) \ case CKM_##MECH: \ { \ static const struct sockaddr_alg addr = \ { \ .salg_family = AF_ALG, .salg_type = "skcipher", \ .salg_name = (NAME) \ }; \ \ sa = &addr; \ break; \ } \ E(AES_ECB, "ecb(aes)"); E(AES_CBC, "cbc(aes)"); E(DES3_ECB, "ecb(des3_ede)"); E(DES3_CBC, "cbc(des3_ede)"); default: g_return_val_if_reached (CKR_MECHANISM_INVALID); } *sa_ptr = sa; return CKR_OK; } struct ncr_symm_cipher_session { int parent_fd, child_fd; CK_MECHANISM_TYPE mech; bool encrypting; /* Debugging only */ enum { NSCS_NEW, NSCS_INITIALIZED, NSCS_UPDATED, NSCS_FINISHED } state; }; CK_RV ncr_symm_cipher_alloc (struct ncr_symm_cipher_session **sess, CK_MECHANISM_TYPE mech) { struct ncr_symm_cipher_session *s; const struct sockaddr_alg *sa; CK_RV res; g_return_val_if_fail (sess != NULL, CKR_ARGUMENTS_BAD); res = cipher_params_from_mech (mech, &sa); if (res != CKR_OK) return res; s = malloc (sizeof (*s)); if (s == NULL) return CKR_HOST_MEMORY; s->parent_fd = socket (AF_ALG, SOCK_SEQPACKET, 0); if (s->parent_fd == -1) { res = ckr_errno (); goto err_s; } if (bind (s->parent_fd, (struct sockaddr *)sa, sizeof (*sa)) != 0) { res = ckr_errno (); goto err_parent_fd; } s->child_fd = -1; s->mech = mech; s->state = NSCS_NEW; *sess = s; return CKR_OK; err_parent_fd: (void)close (s->parent_fd); err_s: free (s); return res; } CK_RV ncr_symm_cipher_free (struct ncr_symm_cipher_session *sess) { g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); if (sess->child_fd != -1) (void)close (sess->child_fd); (void)close (sess->parent_fd); free (sess); return CKR_OK; } CK_RV ncr_symm_cipher_change_iv (struct ncr_symm_cipher_session *sess, const void *iv, size_t iv_size) { uint8_t cmsg_buf[4096]; /* The size is arbitrary. */ size_t cmsg_space; struct msghdr msg; struct cmsghdr *cmsg; struct af_alg_iv iv_header; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSCS_INITIALIZED || sess->state == NSCS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); g_return_val_if_fail (iv != NULL || iv_size == 0, CKR_ARGUMENTS_BAD); /* Implicitly restricts iv_size so that it fits into iv_header.ivlen */ cmsg_space = CMSG_SPACE (sizeof (iv_header) + iv_size); g_return_val_if_fail (cmsg_space <= sizeof (cmsg_buf), CKR_MECHANISM_PARAM_INVALID); /* The IV might be left pending until the first actual data arrives, saving one system call, but we prefer reporting invalid IV immediately. */ msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = NULL; msg.msg_iovlen = 0; msg.msg_control = cmsg_buf; msg.msg_controllen = cmsg_space; iv_header.ivlen = iv_size; cmsg = CMSG_FIRSTHDR (&msg); g_return_val_if_fail (cmsg != NULL, CKR_GENERAL_ERROR); /* What? */ cmsg->cmsg_len = CMSG_LEN (sizeof (iv_header) + iv_size); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_IV; memcpy (CMSG_DATA (cmsg), &iv_header, sizeof (iv_header)); memcpy (CMSG_DATA (cmsg) + sizeof (iv_header), iv, iv_size); if (sendmsg (sess->child_fd, &msg, MSG_MORE) != 0) return ckr_errno (); return CKR_OK; } static CK_RV symm_cipher_init (struct ncr_symm_cipher_session *sess, bool encrypt, struct ncr_symm_key *key, const void *param, size_t param_size) { uint8_t cmsg_buf[4096]; /* The size is arbitrary. */ size_t cmsg_space; struct msghdr msg; struct cmsghdr *cmsg; struct af_alg_iv iv; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSCS_NEW || sess->state == NSCS_FINISHED, CKR_OPERATION_ACTIVE); g_return_val_if_fail (key != NULL, CKR_KEY_HANDLE_INVALID); /* We need key->size to fit into socklen_t. There is no SOCKLEN_MAX, but it is at least INT32_MAX, which is plenty. */ g_return_val_if_fail (key->size <= INT32_MAX, CKR_KEY_SIZE_RANGE); g_return_val_if_fail (param != NULL || param_size == 0, CKR_ARGUMENTS_BAD); /* Implicitly restricts param_size so that it fits into iv.ivlen */ cmsg_space = CMSG_SPACE (sizeof (iv) + param_size); g_return_val_if_fail (cmsg_space <= sizeof (cmsg_buf), CKR_MECHANISM_PARAM_INVALID); switch (sess->mech) { case CKM_AES_ECB: case CKM_AES_CBC: g_return_val_if_fail (key->type == CKK_AES, CKR_KEY_TYPE_INCONSISTENT); break; case CKM_DES3_ECB: case CKM_DES3_CBC: g_return_val_if_fail (key->type == CKK_DES3, CKR_KEY_TYPE_INCONSISTENT); break; default: g_return_val_if_reached (CKR_MECHANISM_INVALID); } if (setsockopt (sess->parent_fd, SOL_ALG, ALG_SET_KEY, key->value, key->size) != 0) return ckr_errno (); sess->child_fd = accept (sess->parent_fd, NULL, 0); if (sess->child_fd == -1) return ckr_errno (); /* The IV might be left pending until the first actual data arrives, saving one system call, but we prefer reporting invalid IV immediately. */ msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = NULL; msg.msg_iovlen = 0; msg.msg_control = cmsg_buf; msg.msg_controllen = cmsg_space; iv.ivlen = param_size; cmsg = CMSG_FIRSTHDR (&msg); g_return_val_if_fail (cmsg != NULL, CKR_GENERAL_ERROR); /* What? */ cmsg->cmsg_len = CMSG_LEN (sizeof (iv) + param_size); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_IV; memcpy (CMSG_DATA (cmsg), &iv, sizeof (iv)); memcpy (CMSG_DATA (cmsg) + sizeof (iv), param, param_size); if (sendmsg (sess->child_fd, &msg, MSG_MORE) != 0) { CK_RV res; res = ckr_errno (); (void)close (sess->child_fd); sess->child_fd = -1; return res; } sess->encrypting = encrypt; sess->state = NSCS_INITIALIZED; return CKR_OK; } static CK_RV symm_cipher_update (struct ncr_symm_cipher_session *sess, bool encrypt, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { size_t done; uint32_t alg_op; uint8_t cmsg_buf[CMSG_SPACE (sizeof (alg_op))]; struct msghdr msg; struct cmsghdr *cmsg; struct iovec iov; g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (dest_size_ptr != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (src_size <= SSIZE_MAX, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess->state == NSCS_INITIALIZED || sess->state == NSCS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); g_return_val_if_fail (sess->encrypting == encrypt, CKR_OPERATION_NOT_INITIALIZED); if (dest == NULL) { *dest_size_ptr = src_size; return CKR_OK; } if (*dest_size_ptr < src_size) { *dest_size_ptr = src_size; return CKR_BUFFER_TOO_SMALL; } g_return_val_if_fail (dest != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (src != NULL || src_size == 0, CKR_ARGUMENTS_BAD); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsg_buf; msg.msg_controllen = sizeof (cmsg_buf); alg_op = sess->encrypting ? ALG_OP_ENCRYPT : ALG_OP_DECRYPT; cmsg = CMSG_FIRSTHDR (&msg); cmsg->cmsg_len = CMSG_LEN (sizeof (alg_op)); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_OP; memcpy (CMSG_DATA (cmsg), &alg_op, sizeof (alg_op)); done = 0; while (done < src_size) { ssize_t run, ret; iov.iov_base = (uint8_t *)src + done; iov.iov_len = src_size - done; run = sendmsg (sess->child_fd, &msg, MSG_DONTWAIT | MSG_MORE); if (run < 0) /* The IV may have already been changed if some partial data was previously accepted, too bad. */ return ckr_errno (); g_return_val_if_fail (run != 0, CKR_GENERAL_ERROR); /* What? */ ret = recvfrom (sess->child_fd, (uint8_t *)dest + done, run, MSG_DONTWAIT, NULL, 0); if (ret < 0) return ckr_errno (); g_return_val_if_fail (ret == run, CKR_GENERAL_ERROR); done += run; } *dest_size_ptr = src_size; sess->state = NSCS_UPDATED; return CKR_OK; } /* EVP_CipherUpdate + EVP_CipherFinal_ex */ static CK_RV do_symm_cipher_update_final (struct ncr_symm_cipher_session *sess, bool encrypt, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { size_t done; uint32_t alg_op; uint8_t cmsg_buf[CMSG_SPACE (sizeof (alg_op))]; struct msghdr msg; struct cmsghdr *cmsg; struct iovec iov; CK_RV res; /* The caller has verified session and its state. */ g_return_val_if_fail (dest_size_ptr != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (src_size <= SSIZE_MAX, CKR_ARGUMENTS_BAD); g_return_val_if_fail (sess->encrypting == encrypt, CKR_OPERATION_NOT_INITIALIZED); if (dest == NULL) { *dest_size_ptr = src_size; return CKR_OK; } if (*dest_size_ptr < src_size) { *dest_size_ptr = src_size; return CKR_BUFFER_TOO_SMALL; } g_return_val_if_fail (dest != NULL, CKR_ARGUMENTS_BAD); g_return_val_if_fail (src != NULL || src_size == 0, CKR_ARGUMENTS_BAD); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsg_buf; msg.msg_controllen = sizeof (cmsg_buf); alg_op = sess->encrypting ? ALG_OP_ENCRYPT : ALG_OP_DECRYPT; cmsg = CMSG_FIRSTHDR (&msg); cmsg->cmsg_len = CMSG_LEN (sizeof (alg_op)); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_OP; memcpy (CMSG_DATA (cmsg), &alg_op, sizeof (alg_op)); done = 0; while (done < src_size) { ssize_t run, ret; iov.iov_base = (uint8_t *)src + done; iov.iov_len = src_size - done; run = sendmsg (sess->child_fd, &msg, MSG_DONTWAIT /* no MSG_MORE */); if (run < 0) { /* The IV may have already been changed if some partial data was previously accepted, too bad. */ res = ckr_errno (); goto err; } g_return_val_if_fail (run != 0, CKR_GENERAL_ERROR); /* What? */ ret = recvfrom (sess->child_fd, (uint8_t *)dest + done, run, MSG_DONTWAIT, NULL, 0); if (ret < 0) { res = ckr_errno (); goto err; } g_return_val_if_fail (ret == run, CKR_GENERAL_ERROR); done += run; } *dest_size_ptr = src_size; res = CKR_OK; err: (void)close (sess->child_fd); sess->child_fd = -1; sess->state = NSCS_FINISHED; return res; } static CK_RV symm_cipher_final (struct ncr_symm_cipher_session *sess, bool encrypt, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSCS_INITIALIZED || sess->state == NSCS_UPDATED, CKR_OPERATION_NOT_INITIALIZED); return do_symm_cipher_update_final (sess, encrypt, dest, dest_size_ptr, src, src_size); } static CK_RV symm_cipher (struct ncr_symm_cipher_session *sess, bool encrypt, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { g_return_val_if_fail (sess != NULL, CKR_SESSION_HANDLE_INVALID); g_return_val_if_fail (sess->state == NSCS_INITIALIZED, CKR_OPERATION_NOT_INITIALIZED); return do_symm_cipher_update_final (sess, encrypt, dest, dest_size_ptr, src, src_size); } CK_RV ncr_symm_cipher_encrypt_init (struct ncr_symm_cipher_session *sess, struct ncr_symm_key *key, const void *param, size_t param_size) { return symm_cipher_init (sess, true, key, param, param_size); } CK_RV ncr_symm_cipher_encrypt_update (struct ncr_symm_cipher_session *sess, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { return symm_cipher_update (sess, true, dest, dest_size_ptr, src, src_size); } CK_RV ncr_symm_cipher_encrypt_final (struct ncr_symm_cipher_session *sess, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { return symm_cipher_final (sess, true, dest, dest_size_ptr, src, src_size); } CK_RV ncr_symm_cipher_encrypt (struct ncr_symm_cipher_session *sess, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { return symm_cipher (sess, true, dest, dest_size_ptr, src, src_size); } CK_RV ncr_symm_cipher_decrypt_init (struct ncr_symm_cipher_session *sess, struct ncr_symm_key *key, const void *param, size_t param_size) { return symm_cipher_init (sess, false, key, param, param_size); } CK_RV ncr_symm_cipher_decrypt_update (struct ncr_symm_cipher_session *sess, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { return symm_cipher_update (sess, false, dest, dest_size_ptr, src, src_size); } CK_RV ncr_symm_cipher_decrypt_final (struct ncr_symm_cipher_session *sess, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { return symm_cipher_final (sess, false, dest, dest_size_ptr, src, src_size); } CK_RV ncr_symm_cipher_decrypt (struct ncr_symm_cipher_session *sess, void *dest, size_t *dest_size_ptr, const void *src, size_t src_size) { return symm_cipher (sess, false, dest, dest_size_ptr, src, src_size); }