From eeadf2e6470f45ea0275a6019635573f2a7b5a2c Mon Sep 17 00:00:00 2001 From: Apple Date: Tue, 29 Jun 2004 05:26:23 +0000 Subject: [PATCH] securityd-16.tar.gz --- APPLE_LICENSE | 367 ++++++++ TODO | 1 + doc/BLOBFORMAT | 64 ++ etc/CodeEquivalenceCandidates | 26 + etc/Localizable.strings | 11 + etc/StartupParameters.plist | 10 + etc/authorization.plist | 539 +++++++++++ etc/securityd | 5 + etc/securityd.plist | 12 + etc/startup.mk | 66 ++ securityd.xcode/project.pbxproj | 1510 +++++++++++++++++++++++++++++++ src/AuthorizationDBPlist.cpp | 392 ++++++++ src/AuthorizationDBPlist.h | 85 ++ src/AuthorizationEngine.cpp | 329 +++++++ src/AuthorizationEngine.h | 106 +++ src/AuthorizationMechEval.cpp | 196 ++++ src/AuthorizationMechEval.h | 71 ++ src/AuthorizationRule.cpp | 961 ++++++++++++++++++++ src/AuthorizationRule.h | 162 ++++ src/acl_keychain.cpp | 259 ++++++ src/acl_keychain.h | 78 ++ src/acls.cpp | 112 +++ src/acls.h | 93 ++ src/agentquery.cpp | 642 +++++++++++++ src/agentquery.h | 170 ++++ src/authority.cpp | 264 ++++++ src/authority.h | 120 +++ src/child.cpp | 357 ++++++++ src/child.h | 79 ++ src/codesigdb.cpp | 390 ++++++++ src/codesigdb.h | 109 +++ src/connection.cpp | 176 ++++ src/connection.h | 100 ++ src/database.cpp | 143 +++ src/database.h | 170 ++++ src/dbcrypto.cpp | 478 ++++++++++ src/dbcrypto.h | 89 ++ src/entropy.cpp | 143 +++ src/entropy.h | 67 ++ src/flippers.cpp | 87 ++ src/flippers.h | 92 ++ src/generate.cf | 42 + src/generate.mk | 39 + src/generate.pl | 71 ++ src/kcdatabase.cpp | 893 ++++++++++++++++++ src/kcdatabase.h | 207 +++++ src/kckey.cpp | 187 ++++ src/kckey.h | 85 ++ src/key.cpp | 63 ++ src/key.h | 85 ++ src/localdatabase.cpp | 289 ++++++ src/localdatabase.h | 97 ++ src/localkey.cpp | 164 ++++ src/localkey.h | 96 ++ src/main.cpp | 341 +++++++ src/notifications.cpp | 128 +++ src/notifications.h | 116 +++ src/pcsc++.cpp | 231 +++++ src/pcsc++.h | 152 ++++ src/pcscmonitor.cpp | 151 ++++ src/pcscmonitor.h | 71 ++ src/process.cpp | 272 ++++++ src/process.h | 122 +++ src/reader.cpp | 130 +++ src/reader.h | 69 ++ src/securityd.order | 265 ++++++ src/securityserver.h | 60 ++ src/server.cpp | 376 ++++++++ src/server.h | 155 ++++ src/session.cpp | 463 ++++++++++ src/session.h | 170 ++++ src/structure.cpp | 131 +++ src/structure.h | 241 +++++ src/tempdatabase.cpp | 154 ++++ src/tempdatabase.h | 66 ++ src/token.cpp | 91 ++ src/token.h | 63 ++ src/tokendatabase.cpp | 195 ++++ src/tokendatabase.h | 111 +++ src/transition.cpp | 1107 ++++++++++++++++++++++ src/transwalkers.cpp | 90 ++ src/transwalkers.h | 248 +++++ src/yarrowMigTypes.h | 48 + tests/AZNTest.cpp | 83 ++ tests/auth.plist | 19 + tests/exectest.cpp | 122 +++ tests/testacls.cpp | 411 +++++++++ tests/testauth.cpp | 134 +++ tests/testblobs.cpp | 290 ++++++ tests/testclient.cpp | 289 ++++++ tests/testclient.h | 77 ++ tests/testcrypto.cpp | 192 ++++ tests/testutils.cpp | 248 +++++ tests/testutils.h | 98 ++ 94 files changed, 19229 insertions(+) create mode 100644 APPLE_LICENSE create mode 100644 TODO create mode 100644 doc/BLOBFORMAT create mode 100644 etc/CodeEquivalenceCandidates create mode 100644 etc/Localizable.strings create mode 100644 etc/StartupParameters.plist create mode 100644 etc/authorization.plist create mode 100755 etc/securityd create mode 100644 etc/securityd.plist create mode 100644 etc/startup.mk create mode 100644 securityd.xcode/project.pbxproj create mode 100644 src/AuthorizationDBPlist.cpp create mode 100644 src/AuthorizationDBPlist.h create mode 100644 src/AuthorizationEngine.cpp create mode 100644 src/AuthorizationEngine.h create mode 100644 src/AuthorizationMechEval.cpp create mode 100644 src/AuthorizationMechEval.h create mode 100644 src/AuthorizationRule.cpp create mode 100644 src/AuthorizationRule.h create mode 100644 src/acl_keychain.cpp create mode 100644 src/acl_keychain.h create mode 100644 src/acls.cpp create mode 100644 src/acls.h create mode 100644 src/agentquery.cpp create mode 100644 src/agentquery.h create mode 100644 src/authority.cpp create mode 100644 src/authority.h create mode 100644 src/child.cpp create mode 100644 src/child.h create mode 100644 src/codesigdb.cpp create mode 100644 src/codesigdb.h create mode 100644 src/connection.cpp create mode 100644 src/connection.h create mode 100644 src/database.cpp create mode 100644 src/database.h create mode 100644 src/dbcrypto.cpp create mode 100644 src/dbcrypto.h create mode 100644 src/entropy.cpp create mode 100644 src/entropy.h create mode 100644 src/flippers.cpp create mode 100644 src/flippers.h create mode 100644 src/generate.cf create mode 100644 src/generate.mk create mode 100755 src/generate.pl create mode 100644 src/kcdatabase.cpp create mode 100644 src/kcdatabase.h create mode 100644 src/kckey.cpp create mode 100644 src/kckey.h create mode 100644 src/key.cpp create mode 100644 src/key.h create mode 100644 src/localdatabase.cpp create mode 100644 src/localdatabase.h create mode 100644 src/localkey.cpp create mode 100644 src/localkey.h create mode 100644 src/main.cpp create mode 100644 src/notifications.cpp create mode 100644 src/notifications.h create mode 100644 src/pcsc++.cpp create mode 100644 src/pcsc++.h create mode 100644 src/pcscmonitor.cpp create mode 100644 src/pcscmonitor.h create mode 100644 src/process.cpp create mode 100644 src/process.h create mode 100644 src/reader.cpp create mode 100644 src/reader.h create mode 100644 src/securityd.order create mode 100644 src/securityserver.h create mode 100644 src/server.cpp create mode 100644 src/server.h create mode 100644 src/session.cpp create mode 100644 src/session.h create mode 100644 src/structure.cpp create mode 100644 src/structure.h create mode 100644 src/tempdatabase.cpp create mode 100644 src/tempdatabase.h create mode 100644 src/token.cpp create mode 100644 src/token.h create mode 100644 src/tokendatabase.cpp create mode 100644 src/tokendatabase.h create mode 100644 src/transition.cpp create mode 100644 src/transwalkers.cpp create mode 100644 src/transwalkers.h create mode 100644 src/yarrowMigTypes.h create mode 100644 tests/AZNTest.cpp create mode 100644 tests/auth.plist create mode 100644 tests/exectest.cpp create mode 100644 tests/testacls.cpp create mode 100644 tests/testauth.cpp create mode 100644 tests/testblobs.cpp create mode 100644 tests/testclient.cpp create mode 100644 tests/testclient.h create mode 100644 tests/testcrypto.cpp create mode 100644 tests/testutils.cpp create mode 100644 tests/testutils.h diff --git a/APPLE_LICENSE b/APPLE_LICENSE new file mode 100644 index 0000000..fe81a60 --- /dev/null +++ b/APPLE_LICENSE @@ -0,0 +1,367 @@ +APPLE PUBLIC SOURCE LICENSE +Version 2.0 - August 6, 2003 + +Please read this License carefully before downloading this software. +By downloading or using this software, you are agreeing to be bound by +the terms of this License. If you do not or cannot agree to the terms +of this License, please do not download or use the software. + +1. General; Definitions. This License applies to any program or other +work which Apple Computer, Inc. ("Apple") makes publicly available and +which contains a notice placed by Apple identifying such program or +work as "Original Code" and stating that it is subject to the terms of +this Apple Public Source License version 2.0 ("License"). As used in +this License: + +1.1 "Applicable Patent Rights" mean: (a) in the case where Apple is +the grantor of rights, (i) claims of patents that are now or hereafter +acquired, owned by or assigned to Apple and (ii) that cover subject +matter contained in the Original Code, but only to the extent +necessary to use, reproduce and/or distribute the Original Code +without infringement; and (b) in the case where You are the grantor of +rights, (i) claims of patents that are now or hereafter acquired, +owned by or assigned to You and (ii) that cover subject matter in Your +Modifications, taken alone or in combination with Original Code. + +1.2 "Contributor" means any person or entity that creates or +contributes to the creation of Modifications. + +1.3 "Covered Code" means the Original Code, Modifications, the +combination of Original Code and any Modifications, and/or any +respective portions thereof. + +1.4 "Externally Deploy" means: (a) to sublicense, distribute or +otherwise make Covered Code available, directly or indirectly, to +anyone other than You; and/or (b) to use Covered Code, alone or as +part of a Larger Work, in any way to provide a service, including but +not limited to delivery of content, through electronic communication +with a client other than You. + +1.5 "Larger Work" means a work which combines Covered Code or portions +thereof with code not governed by the terms of this License. + +1.6 "Modifications" mean any addition to, deletion from, and/or change +to, the substance and/or structure of the Original Code, any previous +Modifications, the combination of Original Code and any previous +Modifications, and/or any respective portions thereof. When code is +released as a series of files, a Modification is: (a) any addition to +or deletion from the contents of a file containing Covered Code; +and/or (b) any new file or other representation of computer program +statements that contains any part of Covered Code. + +1.7 "Original Code" means (a) the Source Code of a program or other +work as originally made available by Apple under this License, +including the Source Code of any updates or upgrades to such programs +or works made available by Apple under this License, and that has been +expressly identified by Apple as such in the header file(s) of such +work; and (b) the object code compiled from such Source Code and +originally made available by Apple under this License. + +1.8 "Source Code" means the human readable form of a program or other +work that is suitable for making modifications to it, including all +modules it contains, plus any associated interface definition files, +scripts used to control compilation and installation of an executable +(object code). + +1.9 "You" or "Your" means an individual or a legal entity exercising +rights under this License. For legal entities, "You" or "Your" +includes any entity which controls, is controlled by, or is under +common control with, You, where "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of fifty percent +(50%) or more of the outstanding shares or beneficial ownership of +such entity. + +2. Permitted Uses; Conditions & Restrictions. Subject to the terms +and conditions of this License, Apple hereby grants You, effective on +the date You accept this License and download the Original Code, a +world-wide, royalty-free, non-exclusive license, to the extent of +Apple's Applicable Patent Rights and copyrights covering the Original +Code, to do the following: + +2.1 Unmodified Code. You may use, reproduce, display, perform, +internally distribute within Your organization, and Externally Deploy +verbatim, unmodified copies of the Original Code, for commercial or +non-commercial purposes, provided that in each instance: + +(a) You must retain and reproduce in all copies of Original Code the +copyright and other proprietary notices and disclaimers of Apple as +they appear in the Original Code, and keep intact all notices in the +Original Code that refer to this License; and + +(b) You must include a copy of this License with every copy of Source +Code of Covered Code and documentation You distribute or Externally +Deploy, and You may not offer or impose any terms on such Source Code +that alter or restrict this License or the recipients' rights +hereunder, except as permitted under Section 6. + +2.2 Modified Code. You may modify Covered Code and use, reproduce, +display, perform, internally distribute within Your organization, and +Externally Deploy Your Modifications and Covered Code, for commercial +or non-commercial purposes, provided that in each instance You also +meet all of these conditions: + +(a) You must satisfy all the conditions of Section 2.1 with respect to +the Source Code of the Covered Code; + +(b) You must duplicate, to the extent it does not already exist, the +notice in Exhibit A in each file of the Source Code of all Your +Modifications, and cause the modified files to carry prominent notices +stating that You changed the files and the date of any change; and + +(c) If You Externally Deploy Your Modifications, You must make +Source Code of all Your Externally Deployed Modifications either +available to those to whom You have Externally Deployed Your +Modifications, or publicly available. Source Code of Your Externally +Deployed Modifications must be released under the terms set forth in +this License, including the license grants set forth in Section 3 +below, for as long as you Externally Deploy the Covered Code or twelve +(12) months from the date of initial External Deployment, whichever is +longer. You should preferably distribute the Source Code of Your +Externally Deployed Modifications electronically (e.g. download from a +web site). + +2.3 Distribution of Executable Versions. In addition, if You +Externally Deploy Covered Code (Original Code and/or Modifications) in +object code, executable form only, You must include a prominent +notice, in the code itself as well as in related documentation, +stating that Source Code of the Covered Code is available under the +terms of this License with information on how and where to obtain such +Source Code. + +2.4 Third Party Rights. You expressly acknowledge and agree that +although Apple and each Contributor grants the licenses to their +respective portions of the Covered Code set forth herein, no +assurances are provided by Apple or any Contributor that the Covered +Code does not infringe the patent or other intellectual property +rights of any other entity. Apple and each Contributor disclaim any +liability to You for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a +condition to exercising the rights and licenses granted hereunder, You +hereby assume sole responsibility to secure any other intellectual +property rights needed, if any. For example, if a third party patent +license is required to allow You to distribute the Covered Code, it is +Your responsibility to acquire that license before distributing the +Covered Code. + +3. Your Grants. In consideration of, and as a condition to, the +licenses granted to You under this License, You hereby grant to any +person or entity receiving or distributing Covered Code under this +License a non-exclusive, royalty-free, perpetual, irrevocable license, +under Your Applicable Patent Rights and other intellectual property +rights (other than patent) owned or controlled by You, to use, +reproduce, display, perform, modify, sublicense, distribute and +Externally Deploy Your Modifications of the same scope and extent as +Apple's licenses under Sections 2.1 and 2.2 above. + +4. Larger Works. You may create a Larger Work by combining Covered +Code with other code not governed by the terms of this License and +distribute the Larger Work as a single product. In each such instance, +You must make sure the requirements of this License are fulfilled for +the Covered Code or any portion thereof. + +5. Limitations on Patent License. Except as expressly stated in +Section 2, no other patent rights, express or implied, are granted by +Apple herein. Modifications and/or Larger Works may require additional +patent licenses from Apple which Apple may grant in its sole +discretion. + +6. Additional Terms. You may choose to offer, and to charge a fee for, +warranty, support, indemnity or liability obligations and/or other +rights consistent with the scope of the license granted herein +("Additional Terms") to one or more recipients of Covered Code. +However, You may do so only on Your own behalf and as Your sole +responsibility, and not on behalf of Apple or any Contributor. You +must obtain the recipient's agreement that any such Additional Terms +are offered by You alone, and You hereby agree to indemnify, defend +and hold Apple and every Contributor harmless for any liability +incurred by or claims asserted against Apple or such Contributor by +reason of any such Additional Terms. + +7. Versions of the License. Apple may publish revised and/or new +versions of this License from time to time. Each version will be given +a distinguishing version number. Once Original Code has been published +under a particular version of this License, You may continue to use it +under the terms of that version. You may also choose to use such +Original Code under the terms of any subsequent version of this +License published by Apple. No one other than Apple has the right to +modify the terms applicable to Covered Code created under this +License. + +8. NO WARRANTY OR SUPPORT. The Covered Code may contain in whole or in +part pre-release, untested, or not fully tested works. The Covered +Code may contain errors that could cause failures or loss of data, and +may be incomplete or contain inaccuracies. You expressly acknowledge +and agree that use of the Covered Code, or any portion thereof, is at +Your sole and entire risk. THE COVERED CODE IS PROVIDED "AS IS" AND +WITHOUT WARRANTY, UPGRADES OR SUPPORT OF ANY KIND AND APPLE AND +APPLE'S LICENSOR(S) (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE +PURPOSES OF SECTIONS 8 AND 9) AND ALL CONTRIBUTORS EXPRESSLY DISCLAIM +ALL WARRANTIES AND/OR CONDITIONS, EXPRESS OR IMPLIED, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF +MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR +PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NONINFRINGEMENT OF THIRD +PARTY RIGHTS. APPLE AND EACH CONTRIBUTOR DOES NOT WARRANT AGAINST +INTERFERENCE WITH YOUR ENJOYMENT OF THE COVERED CODE, THAT THE +FUNCTIONS CONTAINED IN THE COVERED CODE WILL MEET YOUR REQUIREMENTS, +THAT THE OPERATION OF THE COVERED CODE WILL BE UNINTERRUPTED OR +ERROR-FREE, OR THAT DEFECTS IN THE COVERED CODE WILL BE CORRECTED. NO +ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE, AN APPLE +AUTHORIZED REPRESENTATIVE OR ANY CONTRIBUTOR SHALL CREATE A WARRANTY. +You acknowledge that the Covered Code is not intended for use in the +operation of nuclear facilities, aircraft navigation, communication +systems, or air traffic control machines in which case the failure of +the Covered Code could lead to death, personal injury, or severe +physical or environmental damage. + +9. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO +EVENT SHALL APPLE OR ANY CONTRIBUTOR BE LIABLE FOR ANY INCIDENTAL, +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR RELATING +TO THIS LICENSE OR YOUR USE OR INABILITY TO USE THE COVERED CODE, OR +ANY PORTION THEREOF, WHETHER UNDER A THEORY OF CONTRACT, WARRANTY, +TORT (INCLUDING NEGLIGENCE), PRODUCTS LIABILITY OR OTHERWISE, EVEN IF +APPLE OR SUCH CONTRIBUTOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES AND NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY +REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY OF +INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY +TO YOU. In no event shall Apple's total liability to You for all +damages (other than as may be required by applicable law) under this +License exceed the amount of fifty dollars ($50.00). + +10. Trademarks. This License does not grant any rights to use the +trademarks or trade names "Apple", "Apple Computer", "Mac", "Mac OS", +"QuickTime", "QuickTime Streaming Server" or any other trademarks, +service marks, logos or trade names belonging to Apple (collectively +"Apple Marks") or to any trademark, service mark, logo or trade name +belonging to any Contributor. You agree not to use any Apple Marks in +or as part of the name of products derived from the Original Code or +to endorse or promote products derived from the Original Code other +than as expressly permitted by and in strict compliance at all times +with Apple's third party trademark usage guidelines which are posted +at http://www.apple.com/legal/guidelinesfor3rdparties.html. + +11. Ownership. Subject to the licenses granted under this License, +each Contributor retains all rights, title and interest in and to any +Modifications made by such Contributor. Apple retains all rights, +title and interest in and to the Original Code and any Modifications +made by or on behalf of Apple ("Apple Modifications"), and such Apple +Modifications will not be automatically subject to this License. Apple +may, at its sole discretion, choose to license such Apple +Modifications under this License, or on different terms from those +contained in this License or may choose not to license them at all. + +12. Termination. + +12.1 Termination. This License and the rights granted hereunder will +terminate: + +(a) automatically without notice from Apple if You fail to comply with +any term(s) of this License and fail to cure such breach within 30 +days of becoming aware of such breach; + +(b) immediately in the event of the circumstances described in Section +13.5(b); or + +(c) automatically without notice from Apple if You, at any time during +the term of this License, commence an action for patent infringement +against Apple; provided that Apple did not first commence +an action for patent infringement against You in that instance. + +12.2 Effect of Termination. Upon termination, You agree to immediately +stop any further use, reproduction, modification, sublicensing and +distribution of the Covered Code. All sublicenses to the Covered Code +which have been properly granted prior to termination shall survive +any termination of this License. Provisions which, by their nature, +should remain in effect beyond the termination of this License shall +survive, including but not limited to Sections 3, 5, 8, 9, 10, 11, +12.2 and 13. No party will be liable to any other for compensation, +indemnity or damages of any sort solely as a result of terminating +this License in accordance with its terms, and termination of this +License will be without prejudice to any other right or remedy of +any party. + +13. Miscellaneous. + +13.1 Government End Users. The Covered Code is a "commercial item" as +defined in FAR 2.101. Government software and technical data rights in +the Covered Code include only those rights customarily provided to the +public as defined in this License. This customary commercial license +in technical data and software is provided in accordance with FAR +12.211 (Technical Data) and 12.212 (Computer Software) and, for +Department of Defense purchases, DFAR 252.227-7015 (Technical Data -- +Commercial Items) and 227.7202-3 (Rights in Commercial Computer +Software or Computer Software Documentation). Accordingly, all U.S. +Government End Users acquire Covered Code with only those rights set +forth herein. + +13.2 Relationship of Parties. This License will not be construed as +creating an agency, partnership, joint venture or any other form of +legal association between or among You, Apple or any Contributor, and +You will not represent to the contrary, whether expressly, by +implication, appearance or otherwise. + +13.3 Independent Development. Nothing in this License will impair +Apple's right to acquire, license, develop, have others develop for +it, market and/or distribute technology or products that perform the +same or similar functions as, or otherwise compete with, +Modifications, Larger Works, technology or products that You may +develop, produce, market or distribute. + +13.4 Waiver; Construction. Failure by Apple or any Contributor to +enforce any provision of this License will not be deemed a waiver of +future enforcement of that or any other provision. Any law or +regulation which provides that the language of a contract shall be +construed against the drafter will not apply to this License. + +13.5 Severability. (a) If for any reason a court of competent +jurisdiction finds any provision of this License, or portion thereof, +to be unenforceable, that provision of the License will be enforced to +the maximum extent permissible so as to effect the economic benefits +and intent of the parties, and the remainder of this License will +continue in full force and effect. (b) Notwithstanding the foregoing, +if applicable law prohibits or restricts You from fully and/or +specifically complying with Sections 2 and/or 3 or prevents the +enforceability of either of those Sections, this License will +immediately terminate and You must immediately discontinue any use of +the Covered Code and destroy all copies of it that are in your +possession or control. + +13.6 Dispute Resolution. Any litigation or other dispute resolution +between You and Apple relating to this License shall take place in the +Northern District of California, and You and Apple hereby consent to +the personal jurisdiction of, and venue in, the state and federal +courts within that District with respect to this License. The +application of the United Nations Convention on Contracts for the +International Sale of Goods is expressly excluded. + +13.7 Entire Agreement; Governing Law. This License constitutes the +entire agreement between the parties with respect to the subject +matter hereof. This License shall be governed by the laws of the +United States and the State of California, except that body of +California law concerning conflicts of law. + +Where You are located in the province of Quebec, Canada, the following +clause applies: The parties hereby confirm that they have requested +that this License and all related documents be drafted in English. Les +parties ont exige que le present contrat et tous les documents +connexes soient rediges en anglais. + +EXHIBIT A. + +"Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights +Reserved. + +This file contains Original Code and/or Modifications of Original Code +as defined in and that are subject to the Apple Public Source License +Version 2.0 (the 'License'). You may not use this file except in +compliance with the License. Please obtain a copy of the License at +http://www.opensource.apple.com/apsl/ and read it before using this +file. + +The Original Code and all software distributed under the License are +distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, +INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. +Please see the License for the specific language governing rights and +limitations under the License." diff --git a/TODO b/TODO new file mode 100644 index 0000000..f3d66f7 --- /dev/null +++ b/TODO @@ -0,0 +1 @@ +SecurityServer is a work in progress. diff --git a/doc/BLOBFORMAT b/doc/BLOBFORMAT new file mode 100644 index 0000000..22528f7 --- /dev/null +++ b/doc/BLOBFORMAT @@ -0,0 +1,64 @@ +Description of SecurityServer blob format. + + +Database blob: + + +Creation (input PASSWORD, PRIVATE_DBB_BYTES, PUBLIC_DBB_BYTES) +Update -- change password (same as creation except use passed in DSK and DEK). + +1. Generate a 20 byte (160 bit) random string called SALT. +2. Derive a 24 byte (192 bit -- 168 bit effective because of odd parity in each octet) 3DES key called MK and 8 bytes IV from PASSWORD and SALT using PBKDF2 with PRF = HMACSHA1 and iteration count = 1000. +3. Generate a 24 byte (192 bit -- 168 bit effective because of parity) 3DES key called DEK. +4. Generate a 20 byte (160 bit SHA1HMAC key called DSK. +5. Let TEMP1 = DSK || DEK || PRIVATE_DBB_BYTES +6. Let TEMP2 = ciphertext of TEMP1 encrypted with MK and IV using 3DES in CBC_IV mode. +7. Let TEMP3 = SALT || LEN(PUBLIC_DBB_BYTES) || PUBLIC_DBB_BYTES || TEMP2 +8. Let SIG = SHA1HMAC(DSK, TEMP3) +9. Let DBB = SIG || TEMP3 +10. Outputs DBB, DSK (for signing) and DEK (for encryption) + + +Decode (input DBB and PASSWORD) + +1. Let SIG = First 20 octets of DBB. +2. Let TEMP3 = Octets 20 though end of DBB. +3. Let SALT = Octets 0 though 20 of TEMP3. +4. Derive a 192 bit (168 bit effective because of parity) 3DES key called MK and 8 bytes IV from PASSWORD and SALT using PKDF2 with PRF = HMACSHA1 and iteration count = 1000. +5. Let LEN_PUBLIC_DBB_BYTES = Octets 20 though 24 of TEMP3. +6. Let PUBLIC_DBB_BYTES = Octets 24 though 24 + LEN_PUBLIC_DBB_BYTES of TEMP3. +7. Let TEMP2 = Octets 24 + LEN_PUBLIC_DBB_BYTES though end of TEMP3. +8. Let TEMP1 = plaintext of TEMP2 decrypted with MK and IV using 3DES in CBC_IV mode with PKCS1 padding. +9. Let DSK = First 20 octets of TEMP1 +10. Verify that SHA1HMAC(DSK, TEMP3) == SIG (using VerifyMac) if fail then password is wrong. +11. Let DEK = Octets 20 though 44 of TEMP1 +12. If DEK does not have odd parity in all octets then DBB is corrupt. +13. Let PRIVATE_DBB_BYTES = Octets 44 though end of TEMP1 +14. Outputs PUBLIC_DBB_BYTES, PRIVATE_DBB_BYTES, DSK, DEK + + + +Key blob: + +Creation (input DSK, DEK, PRIVATE_KEY_BYTES, PUBLIC_KEY_BYTES output KB) +(NOTE PRIVATE_KEY_BYTES contains both the key bits (24 bytes) and the private ACL parts) + +1. Generate a 8 byte random string called IV +2. Encrypt PRIVATE_KEY_BYTES using DEK (3DES) and IV in CBC mode with PKCS1 padding. Call the ciphertext TEMP1 +3. Let TEMP2 = IV || TEMP1. +4. Reverse the order of the octects in TEMP2 call the result TEMP3. +5. Encrypt TEMP3 using DEK with an IV of 0x4adda22c79e82105 in CBC mode with PKCS1 padding call the result TEMP4. +6. Concatenate LEN(PUBLIC_KEY_BYTES) | PUBLIC_KEY_BYTES | TEMP4 and call it TEMP5 +7. Compute the 20 byte SHA1HMAC of TEMP5 using DSK and call it SIG. +8. Concatinate TEMP5 | SIG and call the result KB. + +Decode (input DSK, DEK, KB output PRIVATE_KEY_BYTES, PUBLIC_KEY_BYTES) + +1. Split KB in TEMP5 and SIG (SIG is last 20 bytes) TEMP5 is the rest. +2. Verify the 20 byte SHA1HMAC of TEMP5 using DSK against SIG if if fails the blob is invalid. +3. Split TEMP5 in LEN(PUBLIC_KEY_BYTES) , PUBLIC_KEY_BYTES and TEMP4. +4. Decrypt TEMP4 using DEK with an IV of 0x4adda22c79e82105 in CBC mode with PKCS1 padding call the result TEMP3. +5. Reverse the order of the octects in TEMP3 and call the result TEMP2. +6. Split TEMP2 in IV (first 8 bytes) and TEMP1 (rest). +7. Decrypt TEMP1 using DEK (3DES) and IV in CBC mode with PKCS1 padding. Call the plaintext PRIVATE_KEY_BYTES. + diff --git a/etc/CodeEquivalenceCandidates b/etc/CodeEquivalenceCandidates new file mode 100644 index 0000000..77721b5 --- /dev/null +++ b/etc/CodeEquivalenceCandidates @@ -0,0 +1,26 @@ +/System/Library/CoreServices/Finder.app +/Applications/iChat.app +/Applications/iSync.app +/Applications/Mail.app +/Applications/Safari.app +/Applications/Utilities/AirPort Admin Utility.app +/Applications/Utilities/Keychain Access.app +/Applications/Utilities/Keychain Access.app/Contents/Resources/Keychain Agent +/System/Library/CoreServices/loginwindow.app +/System/Library/CoreServices/MirrorAgent.app +/System/Library/CoreServices/SecurityAgent.app +/System/Library/Filesystems/ftp.fs/mount_ftp +/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/Support/AEServer +/System/Library/PrivateFrameworks/Admin.framework/Versions/A/Resources/writeconfig +/System/Library/PrivateFrameworks/InstantMessage.framework/iChatAgent.app +/System/Library/ScriptingAdditions/Keychain Scripting.app +/sbin/mount_smbfs +/sbin/mount_webdav +/usr/bin/certtool +/usr/bin/crlrefresh +/usr/bin/security +/usr/bin/smbutil +/usr/local/bin/cmsutil +/usr/sbin/pppd +/usr/sbin/racoon +/usr/sbin/systemkeychain diff --git a/etc/Localizable.strings b/etc/Localizable.strings new file mode 100644 index 0000000..2d6f3c5 --- /dev/null +++ b/etc/Localizable.strings @@ -0,0 +1,11 @@ + + + + + Starting SecurityServer + Starting SecurityServer + Stopping SecurityServer + Stopping SecurityServer + + + diff --git a/etc/StartupParameters.plist b/etc/StartupParameters.plist new file mode 100644 index 0000000..8f4fd0e --- /dev/null +++ b/etc/StartupParameters.plist @@ -0,0 +1,10 @@ +{ + Description = "Apple Security Server"; + Provides = ("SecurityServer"); + OrderPreference = "None"; + Messages = + { + start = "Starting SecurityServer"; + stop = "Stopping SecurityServer"; + }; +} diff --git a/etc/authorization.plist b/etc/authorization.plist new file mode 100644 index 0000000..9f197a1 --- /dev/null +++ b/etc/authorization.plist @@ -0,0 +1,539 @@ + + + + + comment + The name of the requested right is matched against the keys. An exact match has priority, otherwise the longest match from the start is used. Note that the right will only match wildcard rules (ending in a ".") during this reduction. + +allow rule: this is always allowed +<key>com.apple.TestApp.benign</key> +<string>allow</string> + +deny rule: this is always denied +<key>com.apple.TestApp.dangerous</key> +<string>deny</string> + +user rule: successful authentication as a user in the specified group(5) allows the associated right. + +The shared property specifies whether a credential generated on success is shared with other apps (same "session"). This property defaults to false if not specified. + +The timeout property specifies the maximum age of a (cached/shared) credential accepted for this rule. + +The allow-root property specifies whether a right should be allowed automatically if the requesting process is running with uid == 0. This defaults to false if not specified. + +See remaining rules for examples. + + rights + + + + class + rule + comment + All other rights will be matched by this rule. +Credentials remain valid 5 minutes after they've been obtained. +An acquired credential is shared amongst all clients. + + rule + default + + config.add. + + class + allow + comment + wildcard right for adding rights. Anyone is allowed to add any (non-wildcard) rights + + config.config. + + class + deny + comment + wildcard right for any change to meta-rights for db modification. Not allowed programmatically (just edit this file) + + config.modify. + + class + rule + comment + wildcard right for modifying rights. Admins are allowed to modify any (non-wildcard) rights + rule + authenticate-admin + + config.remove. + + class + rule + comment + wildcard right for deleting rights. Admins are allowed to delete any (non-wildcard) rights + rule + authenticate-admin + + config.remove.system. + + class + deny + comment + wildcard right for deleting system rights. + + sys.openfile. + + class + user + comment + See authopen(1) for information on the use of this right. + group + admin + mechanisms + + builtin:authenticate + + shared + + timeout + 300 + + system.device.dvd.setregion.initial + + class + user + comment + Used by the dvd player to set the regioncode the first time. Note that changed the region code after it has been set requires a different right (system.device.dvd.setregion.change) +Credentials remain valid indefinitely after they've been obtained. +An acquired credential is shared amongst all clients. + group + admin + mechanisms + + builtin:authenticate + + shared + + + system.login.console + + class + evaluate-mechanisms + comment + Login mechanism based rule. Not for general use, yet. +builtin:krb5authenticate can be used to hinge local authentication on a successful kerberos authentication and kdc verification. +builtin:krb5authnoverify skips the kdc verification. Both fall back on local authentication. + mechanisms + + loginwindow_builtin:login + authinternal + HomeDirMechanism:login + MCXMechanism:login + loginwindow_builtin:success + builtin:getuserinfo + builtin:sso + loginwindow_builtin:done + + + system.login.done + + class + evaluate-mechanisms + comment + builtin:krb5login can be used to do kerberos authentication as a side-effect of logging in. Local username/password will be used. + mechanisms + + + + system.login.pam + + class + evaluate-mechanisms + tries + 1 + mechanisms + + push_hints_to_context + authinternal + + + system.login.screensaver + + class + rule + comment + the owner as well as any admin can unlock the screensaver;modify the group key to change this. + rule + authenticate-session-owner-or-admin + + system.login.tty + + class + evaluate-mechanisms + tries + 1 + mechanisms + + push_hints_to_context + authinternal + + + system.keychain.create.loginkc + + allow-root + + class + evaluate-mechanisms + comment + Used by Security framework when you add an item to a unconfigured default keychain + mechanisms + + loginKC:queryCreate + loginKC:showPasswordUI + authinternal + + session-owner + + shared + + + system.keychain.modify + + class + user + comment + Used by Keychain Access when editing a system keychain. + group + admin + mechanisms + + builtin:authenticate + + shared + + timeout + 300 + + system.preferences + + allow-root + + class + user + comment + This right is checked by the Admin framework when making changes to the system preferences. +Credentials remain valid forever. +An acquired credential is shared amongst all clients. +If the proccess that created the AuthorizationRef has uid = 0 this right will automatically be granted. + group + admin + mechanisms + + builtin:authenticate + + shared + + + system.printingmanager + + class + rule + comment + The following right is checked for printing to locked printers. + rule + authenticate-admin + + system.privilege.admin + + allow-root + + class + user + comment + Used by AuthorizationExecuteWithPrivileges(...) + AuthorizationExecuteWithPrivileges is used by programs requesting + to run a tool as root (ie. some installers). + Credentials remain valid 5 minutes after they've been obtained. + An acquired credential isn't shared with other clients. + Clients running as root will be granted this right automatically. + + group + admin + mechanisms + + builtin:authenticate + + shared + + timeout + 300 + + system.restart + + class + evaluate-mechanisms + comment + Multisession restart mechanisms + mechanisms + + RestartAuthorization:restart + RestartAuthorization:authenticate + RestartAuthorization:success + + + system.shutdown + + class + evaluate-mechanisms + comment + Multisession shutdown mechanisms + mechanisms + + RestartAuthorization:shutdown + RestartAuthorization:authenticate + RestartAuthorization:success + + + system.burn + + class + allow + comment + authorization to burn media + + com.apple.server.admin.streaming + + class + user + comment + Used for admin requests with the QuickTime Streaming Server. + group + admin + shared + + allow-root + + timeout + 0 + + system.install.admin.user + + class + user + comment + Used by installer tool: user installling in admin domain (/Applications) + group + admin + mechanisms + + builtin:authenticate + + shared + + timeout + 300 + + system.install.root.user + + class + user + comment + Used by installer tool: user installling in root domain (/System) + group + admin + mechanisms + + builtin:authenticate + + shared + + timeout + 300 + + system.install.root.admin + + class + user + comment + Used by installer tool: admin installling in root domain (/System) + group + admin + mechanisms + + builtin:authenticate + + shared + + timeout + 300 + + com.apple.appserver.privilege.admin + + class + rule + comment + Used to determine administrative access to the Application Server management tool. + rule + appserver-admin + + com.apple.appserver.privilege.user + + class + rule + comment + Used to determine user access to the Application Server management tool. + k-of-n + 1 + rule + + appserver-admin + appserver-user + + + com.apple.desktopservices + + class + user + comment + authorize privileged file operations from the finder + group + admin + mechanisms + + builtin:authenticate + + shared + + timeout + 0 + + com.apple.builtin.generic-new-passphrase + + class + evaluate-mechanisms + mechanisms + + builtin:generic-new-passphrase + + + com.apple.builtin.generic-unlock + + class + evaluate-mechanisms + mechanisms + + builtin:generic-unlock + + + + rules + + allow + + class + allow + comment + allow anyone + + authenticate-admin + + class + user + comment + require the user asking for authorization to authenticate as an admin + group + admin + mechanisms + + builtin:authenticate + + shared + + timeout + 0 + + authenticate-session-owner + + class + user + comment + authenticate session owner + mechanisms + + builtin:authenticate + + session-owner + + + authenticate-session-owner-or-admin + + allow-root + + class + user + comment + the owner as well as any admin can authorize + group + admin + mechanisms + + builtin:authenticate + + session-owner + + shared + + + is-admin + + class + user + comment + verify the user asking for authorization is an admin + group + admin + shared + true + + is-root + + allow-root + + class + user + comment + verify the process that created this authref is root + group + nogroup + + appserver-user + + class + user + group + appserverusr + + appserver-admin + + class + user + group + appserveradm + + default + + class + user + comment + All other rights will be matched by this rule. Credentials remain valid 5 minutes after they've been obtained. +An acquired credential is shared amongst all clients. + + group + admin + mechanisms + + builtin:authenticate + + shared + + timeout + 300 + + + + diff --git a/etc/securityd b/etc/securityd new file mode 100755 index 0000000..d5a444e --- /dev/null +++ b/etc/securityd @@ -0,0 +1,5 @@ +#!/bin/sh + +. /etc/rc.common + +ConsoleMessage "No longer starting SecurityServer via StartupItems" diff --git a/etc/securityd.plist b/etc/securityd.plist new file mode 100644 index 0000000..82d83c3 --- /dev/null +++ b/etc/securityd.plist @@ -0,0 +1,12 @@ + + + + + ServiceName + com.apple.securityd + Command + /usr/sbin/securityd + OnDemand + + + diff --git a/etc/startup.mk b/etc/startup.mk new file mode 100644 index 0000000..720bc18 --- /dev/null +++ b/etc/startup.mk @@ -0,0 +1,66 @@ +# +# Makefile to install the system-startup code for SecurityServer +# + +# wouldn't it be nice if PBX actually $#@?@! defined those? +# (for future ref: CoreOS Makefiles define many standard path vars) +# Note: CORE_SERVICES_DIR should be absolute path in target environment (don't prefix with DSTROOT) +SYSTEM_LIBRARY_DIR=$(DSTROOT)/System/Library +SYSTEM_CORE_SERVICES_DIR=/System/Library/CoreServices +ETC_DIR=$(DSTROOT)/private/etc +AUTHORIZATION_LOCATION=$(ETC_DIR) +AUTHORIZATION_PLIST=$(AUTHORIZATION_LOCATION)/authorization +VARDB=$(DSTROOT)/private/var/db +CANDIDATES=$(VARDB)/CodeEquivalenceCandidates + +OLD_STARTUP_DST=$(SYSTEM_LIBRARY_DIR)/StartupItems/securityd +DST=$(ETC_DIR)/mach_init.d +SRC=$(SRCROOT)/etc + + +# +# The other phases do nothing +# +build: + @echo null build. + +debug: + @echo null debug. + +profile: + @echo null profile. + +# +# Install +# +install: + mkdir -p $(DST) + cp $(SRC)/securityd.plist $(DST) + # Remove all OLD_STARTUP_DST-related actions when the Disks and + # Loginwindow StartupItems have removed their dependencies on + # "SecurityServer" + mkdir -p $(OLD_STARTUP_DST)/Resources/English.lproj + cp $(SRC)/StartupParameters.plist $(OLD_STARTUP_DST) + sed -e "s:@@@:$(SYSTEM_CORE_SERVICES_DIR):g" $(SRC)/securityd >$(OLD_STARTUP_DST)/securityd + cp $(SRC)/Localizable.strings $(OLD_STARTUP_DST)/Resources/English.lproj/Localizable.plist + chown -R root:wheel $(OLD_STARTUP_DST) + chmod 755 $(OLD_STARTUP_DST)/securityd + chmod 644 $(OLD_STARTUP_DST)/StartupParameters.plist + chmod 644 $(OLD_STARTUP_DST)/Resources/English.lproj/Localizable.plist + mkdir -p $(AUTHORIZATION_LOCATION) + cp $(SRC)/authorization.plist $(AUTHORIZATION_PLIST) + chown root:admin $(AUTHORIZATION_PLIST) + chmod 644 $(AUTHORIZATION_PLIST) + mkdir -p $(VARDB) + cp $(SRC)/CodeEquivalenceCandidates $(CANDIDATES) + chown root:admin $(CANDIDATES) + chmod 644 $(CANDIDATES) + +installhdrs: + @echo null installhdrs. + +installsrc: + @echo null installsrc. + +clean: + @echo null clean. diff --git a/securityd.xcode/project.pbxproj b/securityd.xcode/project.pbxproj new file mode 100644 index 0000000..2f4b736 --- /dev/null +++ b/securityd.xcode/project.pbxproj @@ -0,0 +1,1510 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 39; + objects = { + 405845650663B2010083E58C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = AuthorizationMechEval.cpp; + refType = 4; + sourceTree = ""; + }; + 405845660663B2010083E58C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = AuthorizationMechEval.h; + refType = 4; + sourceTree = ""; + }; + 405845670663B2010083E58C = { + fileRef = 405845650663B2010083E58C; + isa = PBXBuildFile; + settings = { + }; + }; + 405845680663B2010083E58C = { + fileRef = 405845660663B2010083E58C; + isa = PBXBuildFile; + settings = { + }; + }; +//400 +//401 +//402 +//403 +//404 +//4C0 +//4C1 +//4C2 +//4C3 +//4C4 + 4C9264970534866F004B0E72 = { + children = ( + 4C9264980534866F004B0E72, + 4C9264990534866F004B0E72, + 4C92649A0534866F004B0E72, + 4C92649B0534866F004B0E72, + 4C92649C0534866F004B0E72, + 4C92649D0534866F004B0E72, + 4C92649E0534866F004B0E72, + 4C92649F0534866F004B0E72, + 4C9264A00534866F004B0E72, + 4C9264A10534866F004B0E72, + 4C9264A20534866F004B0E72, + 4C9264A30534866F004B0E72, + 405845650663B2010083E58C, + 405845660663B2010083E58C, + 4C9264A40534866F004B0E72, + 4C9264A50534866F004B0E72, + 4CB5ACB906680AE000F359A9, + 4CB5ACBA06680AE000F359A9, + 4C9264A80534866F004B0E72, + 4C9264A90534866F004B0E72, + 4C9264AA0534866F004B0E72, + 4C9264AB0534866F004B0E72, + C2B8DBC705E6C3CE00E6E67C, + C2B8DBC805E6C3CE00E6E67C, + 4C9264AC0534866F004B0E72, + 4C9264AD0534866F004B0E72, + 4C9264AE0534866F004B0E72, + 4C9264AF0534866F004B0E72, + 4C9264B00534866F004B0E72, + 4C9264B10534866F004B0E72, + 4C9264B20534866F004B0E72, + 4C9264B30534866F004B0E72, + 4C9264B40534866F004B0E72, + C2B8DBC905E6C3CE00E6E67C, + C2B8DBCA05E6C3CE00E6E67C, + C207646305EAD713004FEEDA, + C207646405EAD713004FEEDA, + 4C9264B50534866F004B0E72, + 4C9264B60534866F004B0E72, + C20764E405ED250F004FEEDA, + C20764E505ED250F004FEEDA, + C20764E605ED250F004FEEDA, + C20764E705ED250F004FEEDA, + 4C9264B70534866F004B0E72, + 4C9264B80534866F004B0E72, + 4C9264B90534866F004B0E72, + C2FDCABD0663CD5B0013F64C, + C2FDCABE0663CD5B0013F64C, + 4C9264BA0534866F004B0E72, + 4C9264BB0534866F004B0E72, + C2FDCABF0663CD5B0013F64C, + C2FDCAC00663CD5B0013F64C, + 4C9264BC0534866F004B0E72, + 4C9264BD0534866F004B0E72, + 4C9264BE0534866F004B0E72, + 4C9264BF0534866F004B0E72, + 4C9264C00534866F004B0E72, + 4C9264C10534866F004B0E72, + C28ACF9A05C9940B00447176, + C28ACF9B05C9940B00447176, + C20AF37C05F689540055732C, + C20AF37D05F689540055732C, + C2FDCAC10663CD5B0013F64C, + C2FDCAC20663CD5B0013F64C, + C2D425F105F3C07400CB11F8, + C2D425F205F3C07400CB11F8, + 4C9264C20534866F004B0E72, + 4C9264C30534866F004B0E72, + 4C9264C40534866F004B0E72, + 4C9264C70534866F004B0E72, + ); + isa = PBXGroup; + path = src; + refType = 4; + sourceTree = ""; + }; + 4C9264980534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = acl_keychain.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264990534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = acl_keychain.h; + refType = 4; + sourceTree = ""; + }; + 4C92649A0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = acls.cpp; + refType = 4; + sourceTree = ""; + }; + 4C92649B0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = acls.h; + refType = 4; + sourceTree = ""; + }; + 4C92649C0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = agentquery.cpp; + refType = 4; + sourceTree = ""; + }; + 4C92649D0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = agentquery.h; + refType = 4; + sourceTree = ""; + }; + 4C92649E0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = authority.cpp; + refType = 4; + sourceTree = ""; + }; + 4C92649F0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = authority.h; + refType = 4; + sourceTree = ""; + }; + 4C9264A00534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = AuthorizationDBPlist.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264A10534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = AuthorizationDBPlist.h; + refType = 4; + sourceTree = ""; + }; + 4C9264A20534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = AuthorizationEngine.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264A30534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = AuthorizationEngine.h; + refType = 4; + sourceTree = ""; + }; + 4C9264A40534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = AuthorizationRule.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264A50534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = AuthorizationRule.h; + refType = 4; + sourceTree = ""; + }; + 4C9264A80534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = codesigdb.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264A90534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = codesigdb.h; + refType = 4; + sourceTree = ""; + }; + 4C9264AA0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = connection.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264AB0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = connection.h; + refType = 4; + sourceTree = ""; + }; + 4C9264AC0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = dbcrypto.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264AD0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = dbcrypto.h; + refType = 4; + sourceTree = ""; + }; + 4C9264AE0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = entropy.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264AF0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = entropy.h; + refType = 4; + sourceTree = ""; + }; + 4C9264B00534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = flippers.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264B10534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = flippers.h; + refType = 4; + sourceTree = ""; + }; + 4C9264B20534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text; + path = generate.cf; + refType = 4; + sourceTree = ""; + }; + 4C9264B30534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text; + path = generate.mk; + refType = 4; + sourceTree = ""; + }; + 4C9264B40534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text.script.perl; + path = generate.pl; + refType = 4; + sourceTree = ""; + }; + 4C9264B50534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = key.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264B60534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = key.h; + refType = 4; + sourceTree = ""; + }; + 4C9264B70534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = main.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264B80534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = notifications.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264B90534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = notifications.h; + refType = 4; + sourceTree = ""; + }; + 4C9264BA0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = process.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264BB0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = process.h; + refType = 4; + sourceTree = ""; + }; + 4C9264BC0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text; + path = securityd.order; + refType = 4; + sourceTree = ""; + }; + 4C9264BD0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = securityserver.h; + refType = 4; + sourceTree = ""; + }; + 4C9264BE0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = server.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264BF0534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = server.h; + refType = 4; + sourceTree = ""; + }; + 4C9264C00534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = session.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264C10534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = session.h; + refType = 4; + sourceTree = ""; + }; + 4C9264C20534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = transition.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264C30534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = transwalkers.cpp; + refType = 4; + sourceTree = ""; + }; + 4C9264C40534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = transwalkers.h; + refType = 4; + sourceTree = ""; + }; + 4C9264C70534866F004B0E72 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = yarrowMigTypes.h; + refType = 4; + sourceTree = ""; + }; + 4C9264C80534866F004B0E72 = { + fileRef = 4C9264980534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264C90534866F004B0E72 = { + fileRef = 4C9264990534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264CA0534866F004B0E72 = { + fileRef = 4C92649A0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264CB0534866F004B0E72 = { + fileRef = 4C92649B0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264CC0534866F004B0E72 = { + fileRef = 4C92649C0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264CD0534866F004B0E72 = { + fileRef = 4C92649D0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264CE0534866F004B0E72 = { + fileRef = 4C92649E0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264CF0534866F004B0E72 = { + fileRef = 4C92649F0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264D00534866F004B0E72 = { + fileRef = 4C9264A00534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264D10534866F004B0E72 = { + fileRef = 4C9264A10534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264D20534866F004B0E72 = { + fileRef = 4C9264A20534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264D30534866F004B0E72 = { + fileRef = 4C9264A30534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264D40534866F004B0E72 = { + fileRef = 4C9264A40534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264D50534866F004B0E72 = { + fileRef = 4C9264A50534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264D80534866F004B0E72 = { + fileRef = 4C9264A80534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264D90534866F004B0E72 = { + fileRef = 4C9264A90534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264DA0534866F004B0E72 = { + fileRef = 4C9264AA0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264DB0534866F004B0E72 = { + fileRef = 4C9264AB0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264DC0534866F004B0E72 = { + fileRef = 4C9264AC0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264DD0534866F004B0E72 = { + fileRef = 4C9264AD0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264DE0534866F004B0E72 = { + fileRef = 4C9264AE0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264DF0534866F004B0E72 = { + fileRef = 4C9264AF0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E00534866F004B0E72 = { + fileRef = 4C9264B00534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E10534866F004B0E72 = { + fileRef = 4C9264B10534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E20534866F004B0E72 = { + fileRef = 4C9264B50534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E30534866F004B0E72 = { + fileRef = 4C9264B60534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E40534866F004B0E72 = { + fileRef = 4C9264B70534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E50534866F004B0E72 = { + fileRef = 4C9264B80534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E60534866F004B0E72 = { + fileRef = 4C9264B90534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E70534866F004B0E72 = { + fileRef = 4C9264BA0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E80534866F004B0E72 = { + fileRef = 4C9264BB0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264E90534866F004B0E72 = { + fileRef = 4C9264BD0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264EA0534866F004B0E72 = { + fileRef = 4C9264BE0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264EB0534866F004B0E72 = { + fileRef = 4C9264BF0534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264EC0534866F004B0E72 = { + fileRef = 4C9264C00534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264ED0534866F004B0E72 = { + fileRef = 4C9264C10534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264EE0534866F004B0E72 = { + fileRef = 4C9264C20534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264EF0534866F004B0E72 = { + fileRef = 4C9264C30534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264F00534866F004B0E72 = { + fileRef = 4C9264C40534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4C9264F30534866F004B0E72 = { + fileRef = 4C9264C70534866F004B0E72; + isa = PBXBuildFile; + settings = { + }; + }; + 4CA1FEAC052A3C5800F22E42 = { + children = ( + C276AAF20663FD7500B57276, + 4C9264970534866F004B0E72, + 4CDD50150537658500FEC36D, + 4CA1FEB7052A3C6D00F22E42, + 4CD8CCBB055884E0006B3584, + ); + isa = PBXGroup; + refType = 4; + sourceTree = ""; + }; + 4CA1FEAE052A3C5800F22E42 = { + buildRules = ( + ); + buildSettings = { + BUILD_VARIANTS = debug; + COPY_PHASE_STRIP = NO; + CSSM_HEADERS = "$(BUILT_PRODUCTS_DIR)/Security.framework/Headers"; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + ZERO_LINK = YES; + }; + isa = PBXBuildStyle; + name = Development; + }; + 4CA1FEAF052A3C5800F22E42 = { + buildRules = ( + ); + buildSettings = { + CSSM_HEADERS = "$(BUILT_PRODUCTS_DIR)/Security.framework/Headers"; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + ZERO_LINK = NO; + }; + isa = PBXBuildStyle; + name = Deployment; + }; + 4CA1FEB0052A3C5800F22E42 = { + buildSettings = { + }; + buildStyles = ( + 4CA1FEAE052A3C5800F22E42, + 4CA1FEAF052A3C5800F22E42, + ); + hasScannedForEncodings = 1; + isa = PBXProject; + mainGroup = 4CA1FEAC052A3C5800F22E42; + productRefGroup = 4CA1FEB7052A3C6D00F22E42; + projectDirPath = ""; + targets = ( + 4CA1FEB5052A3C6D00F22E42, + 4CDD4F7A053751FF00FEC36D, + 4CA4EB2C0558848900CF7791, + ); + }; + 4CA1FEB1052A3C6D00F22E42 = { + buildActionMask = 2147483647; + files = ( + 4C9264C90534866F004B0E72, + 4C9264CB0534866F004B0E72, + 4C9264CD0534866F004B0E72, + 4C9264CF0534866F004B0E72, + 4C9264D10534866F004B0E72, + 4C9264D30534866F004B0E72, + 405845680663B2010083E58C, + 4C9264D50534866F004B0E72, + 4CB5ACBC06680AE000F359A9, + 4C9264D90534866F004B0E72, + 4C9264DB0534866F004B0E72, + C2B8DBCC05E6C3CE00E6E67C, + 4C9264DD0534866F004B0E72, + 4C9264DF0534866F004B0E72, + 4C9264E10534866F004B0E72, + C2B8DBCE05E6C3CE00E6E67C, + C207646605EAD713004FEEDA, + 4C9264E30534866F004B0E72, + C20764E905ED250F004FEEDA, + C20764EB05ED250F004FEEDA, + 4C9264E60534866F004B0E72, + C2FDCAC40663CD5B0013F64C, + C2FDCAC60663CD5B0013F64C, + 4C9264E80534866F004B0E72, + C2FDCAC80663CD5B0013F64C, + 4C9264E90534866F004B0E72, + 4C9264EB0534866F004B0E72, + 4C9264ED0534866F004B0E72, + C28ACF9D05C9940B00447176, + C20AF37F05F689540055732C, + C2FDCACA0663CD5B0013F64C, + C2D425F405F3C07400CB11F8, + 4C9264F00534866F004B0E72, + 4C9264F30534866F004B0E72, + ); + isa = PBXHeadersBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 4CA1FEB2052A3C6D00F22E42 = { + buildActionMask = 2147483647; + files = ( + 4C9264C80534866F004B0E72, + 4C9264CA0534866F004B0E72, + 4C9264CC0534866F004B0E72, + 4C9264CE0534866F004B0E72, + 4C9264D00534866F004B0E72, + 4C9264D20534866F004B0E72, + 405845670663B2010083E58C, + 4C9264D40534866F004B0E72, + 4CB5ACBB06680AE000F359A9, + 4C9264D80534866F004B0E72, + 4C9264DA0534866F004B0E72, + C2B8DBCB05E6C3CE00E6E67C, + 4C9264DC0534866F004B0E72, + 4C9264DE0534866F004B0E72, + 4C9264E00534866F004B0E72, + C2B8DBCD05E6C3CE00E6E67C, + C207646505EAD713004FEEDA, + 4C9264E20534866F004B0E72, + C20764E805ED250F004FEEDA, + C20764EA05ED250F004FEEDA, + 4C9264E40534866F004B0E72, + 4C9264E50534866F004B0E72, + C2FDCAC30663CD5B0013F64C, + C2FDCAC50663CD5B0013F64C, + 4C9264E70534866F004B0E72, + C2FDCAC70663CD5B0013F64C, + 4C9264EA0534866F004B0E72, + 4C9264EC0534866F004B0E72, + C28ACF9C05C9940B00447176, + C20AF37E05F689540055732C, + C2FDCAC90663CD5B0013F64C, + C2D425F305F3C07400CB11F8, + 4C9264EE0534866F004B0E72, + 4C9264EF0534866F004B0E72, + ); + isa = PBXSourcesBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 4CA1FEB3052A3C6D00F22E42 = { + buildActionMask = 2147483647; + files = ( + C276AAD70663E7A400B57276, + ); + isa = PBXFrameworksBuildPhase; + runOnlyForDeploymentPostprocessing = 0; + }; + 4CA1FEB5052A3C6D00F22E42 = { + buildPhases = ( + 4CA1FEB1052A3C6D00F22E42, + 4CA1FEB2052A3C6D00F22E42, + 4CA1FEB3052A3C6D00F22E42, + ); + buildSettings = { + BUILD_VARIANTS = "normal debug profile"; + CURRENT_PROJECT_VERSION = 16; + FRAMEWORK_SEARCH_PATHS = "/usr/local/SecurityPieces/Frameworks /usr/local/SecurityPieces/Components/securityd"; + INSTALL_PATH = /usr/sbin; + OTHER_ASFLAGS_debug = "$(OTHER_CFLAGS)"; + OTHER_ASFLAGS_normal = "-DNDEBUG $(OTHER_CFLAGS)"; + OTHER_ASFLAGS_profile = "-DNDEBUG $(OTHER_CFLAGS) -pg"; + OTHER_CFLAGS = ""; + OTHER_CFLAGS_debug = "$(OTHER_CFLAGS) -O0 -fno-inline"; + OTHER_CFLAGS_normal = "-DNDEBUG $(OTHER_CFLAGS)"; + OTHER_CFLAGS_profile = "-DNDEBUG $(OTHER_CFLAGS) -pg"; + OTHER_CPLUSPLUSFLAGS_debug = "$(OTHER_CFLAGS) -O0 -fno-inline"; + OTHER_CPLUSPLUSFLAGS_normal = "-DNDEBUG $(OTHER_CFLAGS)"; + OTHER_CPLUSPLUSFLAGS_profile = "-DNDEBUG $(OTHER_CFLAGS) -pg"; + OTHER_LDFLAGS = ""; + OTHER_LDFLAGS_debug = "$(OTHER_LDFLAGS) \"-framework\" \"Security,_debug\" \"-framework\" \"IOKit\" \"-framework\" \"CoreFoundation\" \"-framework\" \"security_agent_client,_debug\" \"-framework\" \"security_cdsa_client,_debug\" \"-framework\" \"securityd_server,_debug\" \"-framework\" \"securityd_client,_debug\" \"-framework\" \"security_cdsa_utilities,_debug\" \"-framework\" \"security_utilities,_debug\""; + OTHER_LDFLAGS_normal = "$(OTHER_LDFLAGS) \"-framework\" \"Security\" \"-framework\" \"IOKit\" \"-framework\" \"CoreFoundation\" \"-framework\" \"security_agent_client\" \"-framework\" \"security_cdsa_client\" \"-framework\" \"securityd_server\" \"-framework\" \"securityd_client\" \"-framework\" \"security_cdsa_utilities\" \"-framework\" \"security_utilities\""; + OTHER_LDFLAGS_profile = "$(OTHER_LDFLAGS) \"-framework\" \"Security,_profile\" \"-framework\" \"IOKit\" \"-framework\" \"CoreFoundation\" \"-framework\" \"security_agent_client,_profile\" \"-framework\" \"security_cdsa_client,_profile\" \"-framework\" \"securityd_server,_profile\" \"-framework\" \"securityd_client,_profile\" \"-framework\" \"security_cdsa_utilities,_profile\" \"-framework\" \"security_utilities,_profile\" -pg"; + PRODUCT_NAME = securityd; + SECTORDER_FLAGS = "-sectorder __TEXT __text src/securityd.order -e start"; + VERSIONING_SYSTEM = "apple-generic"; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + }; + dependencies = ( + 4CDD4FB80537552600FEC36D, + 4CD8CCB6055884BD006B3584, + ); + isa = PBXToolTarget; + name = securityd; + productInstallPath = /usr/sbin; + productName = securityd; + productReference = 4CA1FEB6052A3C6D00F22E42; + }; + 4CA1FEB6052A3C6D00F22E42 = { + explicitFileType = "compiled.mach-o.executable"; + isa = PBXFileReference; + path = securityd; + refType = 3; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 4CA1FEB7052A3C6D00F22E42 = { + children = ( + 4CA1FEB6052A3C6D00F22E42, + 4CDD4F7B053751FF00FEC36D, + ); + isa = PBXGroup; + name = Products; + refType = 4; + sourceTree = ""; + }; + 4CA4EB2C0558848900CF7791 = { + buildArgumentsString = "-f $(SRCROOT)/etc/startup.mk $(ACTION)"; + buildPhases = ( + ); + buildSettings = { + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = startup; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + }; + buildToolPath = /usr/bin/gnumake; + buildWorkingDirectory = ""; + dependencies = ( + ); + isa = PBXLegacyTarget; + name = startup; + passBuildSettingsInEnvironment = 1; + productName = startup; + }; + 4CB5ACB906680AE000F359A9 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = child.cpp; + refType = 4; + sourceTree = ""; + }; + 4CB5ACBA06680AE000F359A9 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = child.h; + refType = 4; + sourceTree = ""; + }; + 4CB5ACBB06680AE000F359A9 = { + fileRef = 4CB5ACB906680AE000F359A9; + isa = PBXBuildFile; + settings = { + }; + }; + 4CB5ACBC06680AE000F359A9 = { + fileRef = 4CB5ACBA06680AE000F359A9; + isa = PBXBuildFile; + settings = { + }; + }; + 4CD8CCB5055884BD006B3584 = { + containerPortal = 4CA1FEB0052A3C5800F22E42; + isa = PBXContainerItemProxy; + proxyType = 1; + remoteGlobalIDString = 4CA4EB2C0558848900CF7791; + remoteInfo = startup; + }; + 4CD8CCB6055884BD006B3584 = { + isa = PBXTargetDependency; + target = 4CA4EB2C0558848900CF7791; + targetProxy = 4CD8CCB5055884BD006B3584; + }; + 4CD8CCBB055884E0006B3584 = { + children = ( + 4CD8CCBC055884E0006B3584, + 4CD8CCBD055884E0006B3584, + 4CD8CCBE055884E0006B3584, + 4CD8CCBF055884E0006B3584, + 4CD8CCC0055884E0006B3584, + 4CD8CCC1055884E0006B3584, + ); + isa = PBXGroup; + path = etc; + refType = 4; + sourceTree = ""; + }; + 4CD8CCBC055884E0006B3584 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text.plist; + path = authorization.plist; + refType = 4; + sourceTree = ""; + }; + 4CD8CCBD055884E0006B3584 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text; + path = CodeEquivalenceCandidates; + refType = 4; + sourceTree = ""; + }; + 4CD8CCBE055884E0006B3584 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text.plist.strings; + path = Localizable.strings; + refType = 4; + sourceTree = ""; + }; + 4CD8CCBF055884E0006B3584 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text.script.sh; + path = securityd; + refType = 4; + sourceTree = ""; + }; + 4CD8CCC0055884E0006B3584 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text; + path = startup.mk; + refType = 4; + sourceTree = ""; + }; + 4CD8CCC1055884E0006B3584 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = text.plist; + path = StartupParameters.plist; + refType = 4; + sourceTree = ""; + }; + 4CDD4F79053751FF00FEC36D = { + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + isa = PBXShellScriptBuildPhase; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "THEADER=$BUILT_PRODUCTS_DIR/include/flip_gen.h\nTCPP=$BUILT_PRODUCTS_DIR/include/flip_gen.cpp\nmkdir -p $BUILT_PRODUCTS_DIR/include\nsrc/generate.pl src/generate.cf $THEADER.new $TCPP.new $CSSM_HEADERS/cssmtype.h\ncmp -s $THEADER.new $THEADER || mv $THEADER.new $THEADER\ncmp -s $TCPP.new $TCPP || mv $TCPP.new $TCPP\n"; + }; + 4CDD4F7A053751FF00FEC36D = { + buildPhases = ( + 4CDD4F79053751FF00FEC36D, + ); + buildSettings = { + CSSM_HEADERS = "$(SYSTEM_LIBRARY_DIR)/Frameworks/Security.framework/Headers"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ""; + OTHER_REZFLAGS = ""; + PRODUCT_NAME = flippers; + REZ_EXECUTABLE = YES; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = "-Wmost -Wno-four-char-constants -Wno-unknown-pragmas"; + }; + dependencies = ( + ); + isa = PBXToolTarget; + name = flippers; + productInstallPath = /usr/local/bin; + productName = flippers; + productReference = 4CDD4F7B053751FF00FEC36D; + }; + 4CDD4F7B053751FF00FEC36D = { + explicitFileType = "compiled.mach-o.executable"; + includeInIndex = 0; + isa = PBXFileReference; + path = flippers; + refType = 3; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 4CDD4FB70537552600FEC36D = { + containerPortal = 4CA1FEB0052A3C5800F22E42; + isa = PBXContainerItemProxy; + proxyType = 1; + remoteGlobalIDString = 4CDD4F7A053751FF00FEC36D; + remoteInfo = flippers; + }; + 4CDD4FB80537552600FEC36D = { + isa = PBXTargetDependency; + target = 4CDD4F7A053751FF00FEC36D; + targetProxy = 4CDD4FB70537552600FEC36D; + }; + 4CDD50150537658500FEC36D = { + children = ( + 4CDD5018053765A900FEC36D, + 4CDD506B0537666500FEC36D, + C276AAD60663E7A400B57276, + 4CDD5019053765A900FEC36D, + ); + isa = PBXGroup; + name = "Linked Frameworks"; + path = src; + refType = 4; + sourceTree = ""; + }; + 4CDD5018053765A900FEC36D = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = CoreFoundation.framework; + path = /System/Library/Frameworks/CoreFoundation.framework; + refType = 0; + sourceTree = ""; + }; + 4CDD5019053765A900FEC36D = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = Security.framework; + path = /System/Library/Frameworks/Security.framework; + refType = 0; + sourceTree = ""; + }; + 4CDD506B0537666500FEC36D = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = IOKit.framework; + path = /System/Library/Frameworks/IOKit.framework; + refType = 0; + sourceTree = ""; + }; +//4C0 +//4C1 +//4C2 +//4C3 +//4C4 +//C20 +//C21 +//C22 +//C23 +//C24 + C207646305EAD713004FEEDA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = kckey.cpp; + refType = 4; + sourceTree = ""; + }; + C207646405EAD713004FEEDA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = kckey.h; + refType = 4; + sourceTree = ""; + }; + C207646505EAD713004FEEDA = { + fileRef = C207646305EAD713004FEEDA; + isa = PBXBuildFile; + settings = { + }; + }; + C207646605EAD713004FEEDA = { + fileRef = C207646405EAD713004FEEDA; + isa = PBXBuildFile; + settings = { + }; + }; + C20764E405ED250F004FEEDA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = localdatabase.cpp; + refType = 4; + sourceTree = ""; + }; + C20764E505ED250F004FEEDA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = localdatabase.h; + refType = 4; + sourceTree = ""; + }; + C20764E605ED250F004FEEDA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = localkey.cpp; + refType = 4; + sourceTree = ""; + }; + C20764E705ED250F004FEEDA = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = localkey.h; + refType = 4; + sourceTree = ""; + }; + C20764E805ED250F004FEEDA = { + fileRef = C20764E405ED250F004FEEDA; + isa = PBXBuildFile; + settings = { + }; + }; + C20764E905ED250F004FEEDA = { + fileRef = C20764E505ED250F004FEEDA; + isa = PBXBuildFile; + settings = { + }; + }; + C20764EA05ED250F004FEEDA = { + fileRef = C20764E605ED250F004FEEDA; + isa = PBXBuildFile; + settings = { + }; + }; + C20764EB05ED250F004FEEDA = { + fileRef = C20764E705ED250F004FEEDA; + isa = PBXBuildFile; + settings = { + }; + }; + C20AF37C05F689540055732C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = tempdatabase.cpp; + refType = 4; + sourceTree = ""; + }; + C20AF37D05F689540055732C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = tempdatabase.h; + refType = 4; + sourceTree = ""; + }; + C20AF37E05F689540055732C = { + fileRef = C20AF37C05F689540055732C; + isa = PBXBuildFile; + settings = { + }; + }; + C20AF37F05F689540055732C = { + fileRef = C20AF37D05F689540055732C; + isa = PBXBuildFile; + settings = { + }; + }; + C276AAD60663E7A400B57276 = { + isa = PBXFileReference; + lastKnownFileType = wrapper.framework; + name = PCSC.framework; + path = /System/Library/Frameworks/PCSC.framework; + refType = 0; + sourceTree = ""; + }; + C276AAD70663E7A400B57276 = { + fileRef = C276AAD60663E7A400B57276; + isa = PBXBuildFile; + settings = { + }; + }; + C276AAF20663FD7500B57276 = { + children = ( + C2FDCABB0663CD5B0013F64C, + C2FDCABC0663CD5B0013F64C, + ); + isa = PBXGroup; + name = "Temporary (to utilities)"; + refType = 4; + sourceTree = ""; + }; + C28ACF9A05C9940B00447176 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = structure.cpp; + refType = 4; + sourceTree = ""; + }; + C28ACF9B05C9940B00447176 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = structure.h; + refType = 4; + sourceTree = ""; + }; + C28ACF9C05C9940B00447176 = { + fileRef = C28ACF9A05C9940B00447176; + isa = PBXBuildFile; + settings = { + }; + }; + C28ACF9D05C9940B00447176 = { + fileRef = C28ACF9B05C9940B00447176; + isa = PBXBuildFile; + settings = { + }; + }; + C2B8DBC705E6C3CE00E6E67C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = database.cpp; + refType = 4; + sourceTree = ""; + }; + C2B8DBC805E6C3CE00E6E67C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = database.h; + refType = 4; + sourceTree = ""; + }; + C2B8DBC905E6C3CE00E6E67C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = kcdatabase.cpp; + refType = 4; + sourceTree = ""; + }; + C2B8DBCA05E6C3CE00E6E67C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = kcdatabase.h; + refType = 4; + sourceTree = ""; + }; + C2B8DBCB05E6C3CE00E6E67C = { + fileRef = C2B8DBC705E6C3CE00E6E67C; + isa = PBXBuildFile; + settings = { + }; + }; + C2B8DBCC05E6C3CE00E6E67C = { + fileRef = C2B8DBC805E6C3CE00E6E67C; + isa = PBXBuildFile; + settings = { + }; + }; + C2B8DBCD05E6C3CE00E6E67C = { + fileRef = C2B8DBC905E6C3CE00E6E67C; + isa = PBXBuildFile; + settings = { + }; + }; + C2B8DBCE05E6C3CE00E6E67C = { + fileRef = C2B8DBCA05E6C3CE00E6E67C; + isa = PBXBuildFile; + settings = { + }; + }; + C2D425F105F3C07400CB11F8 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = tokendatabase.cpp; + refType = 4; + sourceTree = ""; + }; + C2D425F205F3C07400CB11F8 = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = tokendatabase.h; + refType = 4; + sourceTree = ""; + }; + C2D425F305F3C07400CB11F8 = { + fileRef = C2D425F105F3C07400CB11F8; + isa = PBXBuildFile; + settings = { + }; + }; + C2D425F405F3C07400CB11F8 = { + fileRef = C2D425F205F3C07400CB11F8; + isa = PBXBuildFile; + settings = { + }; + }; + C2FDCABB0663CD5B0013F64C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + name = "pcsc++.cpp"; + path = "src/pcsc++.cpp"; + refType = 4; + sourceTree = ""; + }; + C2FDCABC0663CD5B0013F64C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + name = "pcsc++.h"; + path = "src/pcsc++.h"; + refType = 4; + sourceTree = ""; + }; + C2FDCABD0663CD5B0013F64C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = pcscmonitor.cpp; + refType = 4; + sourceTree = ""; + }; + C2FDCABE0663CD5B0013F64C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = pcscmonitor.h; + refType = 4; + sourceTree = ""; + }; + C2FDCABF0663CD5B0013F64C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = reader.cpp; + refType = 4; + sourceTree = ""; + }; + C2FDCAC00663CD5B0013F64C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = reader.h; + refType = 4; + sourceTree = ""; + }; + C2FDCAC10663CD5B0013F64C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.cpp.cpp; + path = token.cpp; + refType = 4; + sourceTree = ""; + }; + C2FDCAC20663CD5B0013F64C = { + fileEncoding = 30; + isa = PBXFileReference; + lastKnownFileType = sourcecode.c.h; + path = token.h; + refType = 4; + sourceTree = ""; + }; + C2FDCAC30663CD5B0013F64C = { + fileRef = C2FDCABB0663CD5B0013F64C; + isa = PBXBuildFile; + settings = { + }; + }; + C2FDCAC40663CD5B0013F64C = { + fileRef = C2FDCABC0663CD5B0013F64C; + isa = PBXBuildFile; + settings = { + }; + }; + C2FDCAC50663CD5B0013F64C = { + fileRef = C2FDCABD0663CD5B0013F64C; + isa = PBXBuildFile; + settings = { + }; + }; + C2FDCAC60663CD5B0013F64C = { + fileRef = C2FDCABE0663CD5B0013F64C; + isa = PBXBuildFile; + settings = { + }; + }; + C2FDCAC70663CD5B0013F64C = { + fileRef = C2FDCABF0663CD5B0013F64C; + isa = PBXBuildFile; + settings = { + }; + }; + C2FDCAC80663CD5B0013F64C = { + fileRef = C2FDCAC00663CD5B0013F64C; + isa = PBXBuildFile; + settings = { + }; + }; + C2FDCAC90663CD5B0013F64C = { + fileRef = C2FDCAC10663CD5B0013F64C; + isa = PBXBuildFile; + settings = { + }; + }; + C2FDCACA0663CD5B0013F64C = { + fileRef = C2FDCAC20663CD5B0013F64C; + isa = PBXBuildFile; + settings = { + }; + }; + }; + rootObject = 4CA1FEB0052A3C5800F22E42; +} diff --git a/src/AuthorizationDBPlist.cpp b/src/AuthorizationDBPlist.cpp new file mode 100644 index 0000000..f7b49e1 --- /dev/null +++ b/src/AuthorizationDBPlist.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + * AuthorizationDBPlist.cpp + * Security + * + */ + +#include "AuthorizationDBPlist.h" + +namespace Authorization +{ + +AuthorizationDBPlist::AuthorizationDBPlist(const char *configFile) : mFileName(configFile), mLastChecked(DBL_MIN) +{ + memset(&mRulesFileMtimespec, 0, sizeof(mRulesFileMtimespec)); +} + +void AuthorizationDBPlist::sync(CFAbsoluteTime now) +{ + if (mRules.empty()) + load(now); + else + { + // Don't do anything if we checked the timestamp less than 5 seconds ago + if (mLastChecked > now - 5.0) + return; + + struct stat st; + if (stat(mFileName.c_str(), &st)) + { + Syslog::error("Stating rules file \"%s\": %s", mFileName.c_str(), strerror(errno)); + /* @@@ No rules file found, use defaults: admin group for everything. */ + //UnixError::throwMe(errno); + } + else + { + // @@@ Make sure this is the right way to compare 2 struct timespec thingies + // Technically we should check st_dev and st_ino as well since if either of those change + // we are looking at a different file too. + if (memcmp(&st.st_mtimespec, &mRulesFileMtimespec, sizeof(mRulesFileMtimespec))) + load(now); + } + } + + mLastChecked = now; +} + +void AuthorizationDBPlist::save() const +{ + if (!mConfig) + return; + + StLock _(mReadWriteLock); + + int fd = -1; + string tempFile = mFileName + ","; + + for (;;) + { + fd = open(tempFile.c_str(), O_WRONLY|O_CREAT|O_EXCL, 0644); + if (fd == -1) + { + if (errno == EEXIST) + { + unlink(tempFile.c_str()); + continue; + } + if (errno == EINTR) + continue; + else + break; + } + else + break; + } + + if (fd == -1) + { + Syslog::error("Saving rules file \"%s\": %s", tempFile.c_str(), strerror(errno)); + return; + } + + // convert config to plist + CFDataRef configXML = CFPropertyListCreateXMLData(NULL, mConfig); + + if (!configXML) + return; + + // write out data + SInt32 configSize = CFDataGetLength(configXML); + size_t bytesWritten = write(fd, CFDataGetBytePtr(configXML), configSize); + CFRelease(configXML); + + if (bytesWritten != uint32_t(configSize)) + { + if (bytesWritten == static_cast(-1)) + Syslog::error("Writing rules file \"%s\": %s", tempFile.c_str(), strerror(errno)); + else + Syslog::error("Could only write %lu out of %ld bytes from rules file \"%s\"", + bytesWritten, configSize, tempFile.c_str()); + + close(fd); + unlink(tempFile.c_str()); + } + else + { + close(fd); + if (rename(tempFile.c_str(), mFileName.c_str())) + unlink(tempFile.c_str()); + } + return; +} + +void AuthorizationDBPlist::load(CFTimeInterval now) +{ + StLock _(mReadWriteLock); + + int fd = open(mFileName.c_str(), O_RDONLY, 0); + if (fd == -1) + { + Syslog::error("Opening rules file \"%s\": %s", mFileName.c_str(), strerror(errno)); + return; + } + + struct stat st; + if (fstat(fd, &st)) + { + int error = errno; + close(fd); + UnixError::throwMe(error); + } + + + mRulesFileMtimespec = st.st_mtimespec; + + off_t fileSize = st.st_size; + + CFMutableDataRef xmlData = CFDataCreateMutable(NULL, fileSize); + CFDataSetLength(xmlData, fileSize); + void *buffer = CFDataGetMutableBytePtr(xmlData); + size_t bytesRead = read(fd, buffer, fileSize); + if (bytesRead != fileSize) + { + if (bytesRead == static_cast(-1)) + { + Syslog::error("Reading rules file \"%s\": %s", mFileName.c_str(), strerror(errno)); + CFRelease(xmlData); + return; + } + + Syslog::error("Could only read %ul out of %ul bytes from rules file \"%s\"", + bytesRead, fileSize, mFileName.c_str()); + CFRelease(xmlData); + return; + } + + CFStringRef errorString; + CFDictionaryRef configPlist = reinterpret_cast(CFPropertyListCreateFromXMLData(NULL, xmlData, kCFPropertyListMutableContainersAndLeaves, &errorString)); + + if (!configPlist) + { + char buffer[512]; + const char *error = CFStringGetCStringPtr(errorString, kCFStringEncodingUTF8); + if (error == NULL) + { + if (CFStringGetCString(errorString, buffer, 512, kCFStringEncodingUTF8)) + error = buffer; + } + + Syslog::error("Parsing rules file \"%s\": %s", mFileName.c_str(), error); + if (errorString) + CFRelease(errorString); + + CFRelease(xmlData); + return; + } + + if (CFGetTypeID(configPlist) != CFDictionaryGetTypeID()) + { + + Syslog::error("Rules file \"%s\": is not a dictionary", mFileName.c_str()); + + CFRelease(xmlData); + CFRelease(configPlist); + return; + } + + { + StLock _(mLock); + parseConfig(configPlist); + mLastChecked = now; + } + CFRelease(xmlData); + CFRelease(configPlist); + + close(fd); +} + + + +void +AuthorizationDBPlist::parseConfig(CFDictionaryRef config) +{ + // grab items from top-level dictionary that we care about + CFStringRef rightsKey = CFSTR("rights"); + CFStringRef rulesKey = CFSTR("rules"); + CFMutableDictionaryRef newRights = NULL; + CFMutableDictionaryRef newRules = NULL; + + if (!config) + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule file + + if (CFDictionaryContainsKey(config, rulesKey)) + { + newRules = reinterpret_cast(const_cast(CFDictionaryGetValue(config, rulesKey))); + } + + if (CFDictionaryContainsKey(config, rightsKey)) + { + newRights = reinterpret_cast(const_cast(CFDictionaryGetValue(config, rightsKey))); + } + + if (newRules + && newRights + && (CFDictionaryGetTypeID() == CFGetTypeID(newRules)) + && (CFDictionaryGetTypeID() == CFGetTypeID(newRights))) + { + mConfig = config; + mConfigRights = static_cast(newRights); + mConfigRules = static_cast(newRules); + mRules.clear(); + try + { + CFDictionaryApplyFunction(newRights, parseRule, this); + } + catch (...) + { + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule file + } + } + else + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule file +} + +void AuthorizationDBPlist::parseRule(const void *key, const void *value, void *context) +{ + static_cast(context)->addRight(static_cast(key), static_cast(value)); +} + +void AuthorizationDBPlist::addRight(CFStringRef key, CFDictionaryRef definition) +{ + string keyString = cfString(key); + mRules[keyString] = Rule(keyString, definition, mConfigRules); +} + +bool +AuthorizationDBPlist::validateRule(string inRightName, CFDictionaryRef inRightDefinition) const +{ + try { + Rule newRule(inRightName, inRightDefinition, mConfigRules); + if (newRule->name() == inRightName) + return true; + } + catch (...) + { + secdebug("authrule", "invalid definition for rule %s.\n", inRightName.c_str()); + } + return false; +} + +CFDictionaryRef +AuthorizationDBPlist::getRuleDefinition(string &key) +{ + CFStringRef cfKey = makeCFString(key); + StLock _(mLock); + if (CFDictionaryContainsKey(mConfigRights, cfKey)) + { + CFDictionaryRef definition = reinterpret_cast(const_cast(CFDictionaryGetValue(mConfigRights, cfKey))); + CFRelease(cfKey); + return CFDictionaryCreateCopy(NULL, definition); + } + else + { + CFRelease(cfKey); + return NULL; + } +} + +bool +AuthorizationDBPlist::existRule(string &ruleName) const +{ + map::const_iterator rule = mRules.find(ruleName); + if (rule != mRules.end()) + return true; + + return false; +} + +Rule +AuthorizationDBPlist::getRule(const AuthItemRef &inRight) const +{ + string key(inRight->name()); + // Lock the rulemap + StLock _(mLock); + + if (mRules.empty()) + return Rule(); + + for (;;) + { + map::const_iterator rule = mRules.find(key); + + if (rule != mRules.end()) + return (*rule).second; + + // no default rule + assert (key.size()); + + // any reduction of a combination of two chars is futile + if (key.size() > 2) { + // find last dot with exception of possible dot at end + string::size_type index = key.rfind('.', key.size() - 2); + // cut right after found dot, or make it match default rule + key = key.substr(0, index == string::npos ? 0 : index + 1); + } else + key.erase(); + } +} + +void +AuthorizationDBPlist::setRule(const char *inRightName, CFDictionaryRef inRuleDefinition) +{ + if (!inRuleDefinition || !mConfigRights) + MacOSError::throwMe(errAuthorizationDenied); // errInvalidRule + + CFRef keyRef(CFStringCreateWithCString(NULL, inRightName, kCFStringEncodingASCII)); + if (!keyRef) + return; + + StLock _(mLock); + + CFDictionarySetValue(mConfigRights, keyRef, inRuleDefinition); + // release modification lock here already? + save(); + mLastChecked = 0.0; +} + +void +AuthorizationDBPlist::removeRule(const char *inRightName) +{ + if (!mConfigRights) + MacOSError::throwMe(errAuthorizationDenied); + + CFRef keyRef(CFStringCreateWithCString(NULL, inRightName, kCFStringEncodingASCII)); + if (!keyRef) + return; + + StLock _(mLock); + + if (CFDictionaryContainsKey(mConfigRights, keyRef)) + { + CFDictionaryRemoveValue(mConfigRights, keyRef); + // release modification lock here already? + save(); + mLastChecked = 0.0; + } +} + + +} // end namespace Authorization diff --git a/src/AuthorizationDBPlist.h b/src/AuthorizationDBPlist.h new file mode 100644 index 0000000..4cbd6a5 --- /dev/null +++ b/src/AuthorizationDBPlist.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + * AuthorizationDBPlist.h + * Security + * + */ +#ifndef _H_AUTHORIZATIONDBPLIST +#define _H_AUTHORIZATIONDBPLIST 1 + +#include +#include + +#include +#include "AuthorizationRule.h" + +class AuthorizationDBPlist; // @@@ the ordering sucks here, maybe engine should include all these and other should only include it + +namespace Authorization +{ + +class AuthorizationDBPlist /* : public AuthorizationDB */ +{ +public: + AuthorizationDBPlist(const char *configFile = "/etc/authorization"); + + void sync(CFAbsoluteTime now); + bool validateRule(string inRightName, CFDictionaryRef inRightDefinition) const; + CFDictionaryRef getRuleDefinition(string &key); + + bool existRule(string &ruleName) const; + Rule getRule(const AuthItemRef &inRight) const; + + void setRule(const char *inRightName, CFDictionaryRef inRuleDefinition); + void removeRule(const char *inRightName); + +protected: + void load(CFTimeInterval now); + void save() const; + +private: + string mFileName; + +private: + enum { kTypeRight, kTypeRule }; + void parseConfig(CFDictionaryRef config); + static void parseRule(const void *key, const void *value, void *context); + void addRight(CFStringRef key, CFDictionaryRef definition); + + CFAbsoluteTime mLastChecked; + struct timespec mRulesFileMtimespec; + + map mRules; + CFRef mConfig; + CFRef mConfigRights; + CFRef mConfigRules; + + mutable Mutex mLock; // rule map lock + mutable Mutex mReadWriteLock; // file operation lock +}; + +}; /* namespace Authorization */ + +#endif /* ! _H_AUTHORIZATIONDBPLIST */ diff --git a/src/AuthorizationEngine.cpp b/src/AuthorizationEngine.cpp new file mode 100644 index 0000000..a2d0f6e --- /dev/null +++ b/src/AuthorizationEngine.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +/* + * AuthorizationEngine.cpp + * Authorization + * + * Created by Michael Brouwer on Thu Oct 12 2000. + * + */ +#include "AuthorizationEngine.h" +#include +#include +#include + + +#include "authority.h" + +#include +#include +#include +#include +#include +//#include "session.h" +#include "server.h" + +#include +#include +#include + +#include +#include +#include + +namespace Authorization { + + +// +// Errors to be thrown +// +Error::Error(int err) : error(err) +{ +} + +const char *Error::what() const throw() +{ return "Authorization error"; } + +int Error::unixError() const throw() +{ return error; } // @@@ eventually... + +OSStatus Error::osStatus() const throw() +{ return error; } + +void Error::throwMe(int err) { throw Error(err); } + +// +// Engine class +// +Engine::Engine(const char *configFile) : mAuthdb(configFile) +{ +} + +Engine::~Engine() +{ +} + + +/*! + @function AuthorizationEngine::authorize + + @@@. + + @param inRights (input) List of rights being requested for authorization. + @param environment (optional/input) Environment containing information to be used during evaluation. + @param flags (input) Optional flags @@@ see AuthorizationCreate for a description. + @param inCredentials (input) Credentials already held by the caller. + @param outCredentials (output/optional) Credentials obtained, used or refreshed during this call to authorize the requested rights. + @param outRights (output/optional) Subset of inRights which were actually authorized. + + @results Returns errAuthorizationSuccess if all rights requested are authorized, or if the kAuthorizationFlagPartialRights flag was specified. Might return other status values like errAuthorizationDenied, errAuthorizationCanceled or errAuthorizationInteractionNotAllowed +*/ +OSStatus +Engine::authorize(const AuthItemSet &inRights, const AuthItemSet &environment, + AuthorizationFlags flags, const CredentialSet *inCredentials, CredentialSet *outCredentials, + AuthItemSet &outRights, AuthorizationToken &auth) +{ + CredentialSet credentials; + OSStatus status = errAuthorizationSuccess; + + // Get current time of day. + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + + // Update rules from database if needed + mAuthdb.sync(now); + + // Check if a credential was passed into the environment and we were asked to extend the rights + if (flags & kAuthorizationFlagExtendRights) + { + string username, password; + bool shared = false; + for (AuthItemSet::iterator item = environment.begin(); item != environment.end(); item ++) + { + if (!strcmp((*item)->name(), kAuthorizationEnvironmentUsername)) + username = (*item)->stringValue(); + else if (!strcmp((*item)->name(), kAuthorizationEnvironmentPassword)) + password = (*item)->stringValue(); + else if (!strcmp((*item)->name(), kAuthorizationEnvironmentShared)) + shared = true; + } + + if (username.length()) + { + // Let's create a credential from the passed in username and password. + Credential newCredential(username, password, shared); + // If it's valid insert it into the credentials list. Normally this is + // only done if it actually authorizes a requested right, but for this + // special case (environment) we do it even when no rights are being requested. + if (newCredential->isValid()) + credentials.insert(newCredential); + } + } + + // generate hints for every authorization + AuthItemSet environmentToClient = environment; + + AuthItemSet::const_iterator end = inRights.end(); + for (AuthItemSet::const_iterator it = inRights.begin(); it != end; ++it) + { + // Get the rule for each right we are trying to obtain. + const Rule &toplevelRule = mAuthdb.getRule(*it); + OSStatus result = toplevelRule->evaluate(*it, toplevelRule, environmentToClient, flags, now, inCredentials, credentials, auth); + secdebug("autheval", "evaluate rule %s for right %s returned %ld.", toplevelRule->name().c_str(), (*it)->name(), result); + + { + CodeSigning::OSXCode *processCode = Server::process().clientCode(); + string processName = processCode ? processCode->canonicalPath() : "unknown"; + CodeSigning::OSXCode *authCreatorCode = auth.creatorCode(); + string authCreatorName = authCreatorCode ? authCreatorCode->canonicalPath() : "unknown"; + + if (result == errAuthorizationSuccess) + Syslog::info("Succeeded authorizing right %s by process %s for authorization created by %s.", (*it)->name(), processName.c_str(), authCreatorName.c_str()); + else if (result == errAuthorizationDenied) + Syslog::notice("Failed to authorize right %s by process %s for authorization created by %s.", (*it)->name(), processName.c_str(), authCreatorName.c_str()); + } + + if (result == errAuthorizationSuccess) + outRights.insert(*it); + else if (result == errAuthorizationDenied || result == errAuthorizationInteractionNotAllowed) + { + // add creator pid to authorization token + if (!(flags & kAuthorizationFlagPartialRights)) + { + status = result; + break; + } + } + else if (result == errAuthorizationCanceled) + { + status = result; + break; + } + else + { + Syslog::error("Engine::authorize: Rule::evaluate returned %ld returning errAuthorizationInternal", result); + status = errAuthorizationInternal; + break; + } + } + + if (outCredentials) + outCredentials->swap(credentials); + + return status; +} + +OSStatus +Engine::verifyModification(string inRightName, bool remove, + const CredentialSet *inCredentials, CredentialSet *outCredentials, AuthorizationToken &auth) +{ + // Validate right + + // meta rights are constructed as follows: + // we don't allow setting of wildcard rights, so you can only be more specific + // note that you should never restrict things with a wildcard right without disallowing + // changes to the entire domain. ie. + // system.privilege. -> never + // config.add.system.privilege. -> never + // config.modify.system.privilege. -> never + // config.delete.system.privilege. -> never + // For now we don't allow any configuration of configuration rules + // config.config. -> never + + string rightnameToCheck; + + // @@@ verify right name is is not NULL or zero length + if (inRightName.length() == 0) + return errAuthorizationDenied; + + // @@@ make sure it isn't a wildcard right by checking trailing "." + if ( *(inRightName.rbegin()) == '.') + return errAuthorizationDenied; + + // @@@ make sure it isn't a configure right by checking it doesn't start with config. + if (inRightName.find(kConfigRight, 0) != string::npos) + { + // special handling of meta right change: + // config.add. config.modify. config.remove. config.{}. + // check for config. (which always starts with config.config.) + rightnameToCheck = string(kConfigRight) + inRightName; + } + else + { + // regular check of rights + bool existingRule = mAuthdb.existRule(inRightName); + if (!remove) + { + if (existingRule) + rightnameToCheck = string(kAuthorizationConfigRightModify) + inRightName; + else + rightnameToCheck = string(kAuthorizationConfigRightAdd) + inRightName; + } + else + { + if (existingRule) + rightnameToCheck = string(kAuthorizationConfigRightRemove) + inRightName; + else + { + secdebug("engine", "rule %s doesn't exist.", inRightName.c_str()); + return errAuthorizationSuccess; // doesn't exist, done + } + } + } + + + AuthItemSet rights, environment, outRights; + rights.insert(AuthItemRef(rightnameToCheck.c_str())); + secdebug("engine", "authorizing %s for db modification.", rightnameToCheck.c_str()); + return authorize(rights, environment, kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, inCredentials, outCredentials, outRights, auth); +} + +OSStatus +Engine::getRule(string &inRightName, CFDictionaryRef *outRuleDefinition) +{ + // Get current time of day. + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + + // Update rules from database if needed + mAuthdb.sync(now); + + CFDictionaryRef definition = mAuthdb.getRuleDefinition(inRightName); + if (definition) + { + if (outRuleDefinition) + *outRuleDefinition = definition; + else + CFRelease(definition); + + return errAuthorizationSuccess; + } + + return errAuthorizationDenied; +} + +OSStatus +Engine::setRule(const char *inRightName, CFDictionaryRef inRuleDefinition, const CredentialSet *inCredentials, CredentialSet *outCredentials, AuthorizationToken &auth) +{ + // Get current time of day. + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + + // Update rules from database if needed + mAuthdb.sync(now); + + // Validate rule by constructing it from the passed dictionary + if (!mAuthdb.validateRule(inRightName, inRuleDefinition)) + return errAuthorizationDenied; // @@@ separate error for this? + + OSStatus result = verifyModification(inRightName, false /*setting, not removing*/, inCredentials, outCredentials, auth); + if (result != errAuthorizationSuccess) + return result; + + // set the rule for the right and save the database + mAuthdb.setRule(inRightName, inRuleDefinition); + + return errAuthorizationSuccess; +} + +OSStatus +Engine::removeRule(const char *inRightName, const CredentialSet *inCredentials, CredentialSet *outCredentials, AuthorizationToken &auth) +{ + // Get current time of day. + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + + // Update rules from database if needed + mAuthdb.sync(now); + + OSStatus result = verifyModification(inRightName, true /*removing*/, inCredentials, outCredentials, auth); + if (result != errAuthorizationSuccess) + return result; + + // set the rule for the right and save the database + mAuthdb.removeRule(inRightName); + + return errAuthorizationSuccess; +} + +} // end namespace Authorization diff --git a/src/AuthorizationEngine.h b/src/AuthorizationEngine.h new file mode 100644 index 0000000..6e40250 --- /dev/null +++ b/src/AuthorizationEngine.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + * AuthorizationEngine.h + * Authorization + * + */ +#ifndef _H_AUTHORIZATIONENGINE +#define _H_AUTHORIZATIONENGINE 1 + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "authority.h" + +#include "AuthorizationRule.h" +#include "AuthorizationDBPlist.h" + +namespace Authorization +{ + +class Error : public CommonError { +protected: + Error(int err); +public: + const int error; + virtual int unixError() const throw(); + virtual OSStatus osStatus() const throw(); + virtual const char *what () const throw(); + // @@@ Default value should be internal error. + static void throwMe(int err = -1) __attribute((noreturn)); +}; + + +/* The engine which performs the actual authentication and authorization computations. + + The implementation of a typical call to AuthorizationCreate would look like: + + Get the current shared CredentialSet for this session. + Call authorizedRights() with inRights and the shared CredentialSet. + Compute the difference set between the rights requested and the rights returned from authorizedRights(). + Call credentialIds() with the rights computed above (for which we have no credentials yet). + Call aquireCredentials() for the credentialIds returned from credentialIds() + For each credential returned place it in the session (replacing when needed) if shared() returns true. + The authorization returned to the user should now refer to the credentials in the session and the non shared ones returned by aquireCredentials(). + + When a call to AuthorizationCopyRights() is made, just call authorizedRights() using the union of the session credentials and the credentials tied to the authorization specified. + + When a call to AuthorizationCopyInfo() is made, ask the Credential specified by tag for it info and return it. + + When a call to AuthorizationFree() is made, delete all the non-shared credentials ascociated with the authorization specified. If the kAuthorizationFreeFlagDestroy is set. Also delete the shared credentials ascociated with the authorization specified. + */ +class Engine +{ +public: + Engine(const char *configFile); + ~Engine(); + + OSStatus authorize(const AuthItemSet &inRights, const AuthItemSet &environment, + AuthorizationFlags flags, const CredentialSet *inCredentials, CredentialSet *outCredentials, + AuthItemSet &outRights, AuthorizationToken &auth); + OSStatus getRule(string &inRightName, CFDictionaryRef *outRuleDefinition); + OSStatus setRule(const char *inRightName, CFDictionaryRef inRuleDefinition, const CredentialSet *inCredentials, CredentialSet *outCredentials, AuthorizationToken &auth); + OSStatus removeRule(const char *inRightName, const CredentialSet *inCredentials, CredentialSet *outCredentials, AuthorizationToken &auth); + +private: + OSStatus verifyModification(string inRightName, bool remove, + const CredentialSet *inCredentials, CredentialSet *outCredentials, AuthorizationToken &auth); + + AuthorizationDBPlist mAuthdb; + mutable Mutex mLock; +}; + +}; // namespace Authorization + +#endif /* ! _H_AUTHORIZATIONENGINE */ diff --git a/src/AuthorizationMechEval.cpp b/src/AuthorizationMechEval.cpp new file mode 100644 index 0000000..5f7a793 --- /dev/null +++ b/src/AuthorizationMechEval.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + * AuthorizationMechEval.cpp + * securityd + * + */ + +#include "AuthorizationMechEval.h" + +namespace Authorization { + +AgentMechanismRef::AgentMechanismRef(uid_t clientUID, const Session &session) : + RefPointer(new QueryInvokeMechanism(clientUID, session)) {} + +AgentMechanismRef::AgentMechanismRef() : + RefPointer(new QueryInvokeMechanism()) {} + +// we need the vector of mechanisms +AgentMechanismEvaluator::AgentMechanismEvaluator(uid_t uid, const Session& session, const vector& inMechanisms) : + mMechanisms(inMechanisms), mClientUid(uid), mSession(session) +{ + //set up environment +} + +OSStatus +AgentMechanismEvaluator::run(const AuthValueVector &inArguments, const AuthItemSet &inHints, const AuthItemSet &inContext) +{ + // add process specifics to context? + + vector::const_iterator currentMechanism = mMechanisms.begin(); + + AuthorizationResult result = kAuthorizationResultAllow; + + AuthItemSet hints = inHints; + AuthItemSet context = inContext; + + while ( (result == kAuthorizationResultAllow) && + (currentMechanism != mMechanisms.end()) ) // iterate mechanisms + { + ClientMap::iterator iter = mClients.find(*currentMechanism); + if (iter == mClients.end()) + { + string::size_type extPlugin = currentMechanism->find(':'); + if (extPlugin != string::npos) + { + // no whitespace removal + string pluginIn(currentMechanism->substr(0, extPlugin)); + string mechanismIn(currentMechanism->substr(extPlugin + 1)); + secdebug("AuthEvalMech", "external mech %s:%s", pluginIn.c_str(), mechanismIn.c_str()); + + AgentMechanismRef client(mClientUid, mSession); + client->initialize(pluginIn, mechanismIn); +// XXX/cs client->inferHints(Server::process()); + mClients[*currentMechanism] = client; + } + else if (*currentMechanism == "authinternal") + { + secdebug("AuthEvalMech", "performing authentication"); + result = authinternal(context); + } + else if (*currentMechanism == "push_hints_to_context") + { + secdebug("AuthEvalMech", "evaluate push_hints_to_context"); + result = kAuthorizationResultAllow; // snarfcredential doesn't block evaluation, ever, it may restart + // create out context from input hints, no merge + // @@@ global copy template not being invoked... + context = hints; + } +#if 0 + else if (*currentMechanism == "switch_to_user") + { + AgentMechanismRef client(mClientUid, mSession); + client->terminate(); + } +#endif + else + return errAuthorizationInternal; + } + + iter = mClients.find(*currentMechanism); + + if (iter != mClients.end()) + { + try + { + iter->second->run(inArguments, hints, context, &result); + secdebug("AuthEvalMech", "evaluate(%s) succeeded with result: %lu.", (iter->first).c_str(), result); + } + catch (...) { + secdebug("AuthEvalMech", "exception from mech eval or client death"); + result = kAuthorizationResultUndefined; + } + } + + // we own outHints and outContext + switch(result) + { + case kAuthorizationResultAllow: + secdebug("AuthEvalMech", "result allow"); + currentMechanism++; + break; + case kAuthorizationResultDeny: + secdebug("AuthEvalMech", "result deny"); + break; + case kAuthorizationResultUndefined: + secdebug("AuthEvalMech", "result undefined"); + break; // abort evaluation + case kAuthorizationResultUserCanceled: + secdebug("AuthEvalMech", "result canceled"); + break; // stop evaluation, return some sideband + default: + break; // abort evaluation + } + } + + if ((result == kAuthorizationResultUserCanceled) || + (result == kAuthorizationResultAllow)) + { + mHints = hints; + mContext = context; + } + + // convert AuthorizationResult to OSStatus + switch(result) + { + case kAuthorizationResultDeny: + return errAuthorizationDenied; + case kAuthorizationResultUserCanceled: + return errAuthorizationCanceled; + case kAuthorizationResultAllow: + return errAuthorizationSuccess; + default: + return errAuthorizationInternal; + } +} + +AuthorizationResult AgentMechanismEvaluator::authinternal(AuthItemSet &context) +{ + secdebug("AuthEvalMech", "evaluate authinternal"); + do { + AuthItemSet::iterator found = find_if(context.begin(), context.end(), FindAuthItemByRightName(kAuthorizationEnvironmentUsername) ); + if (found == context.end()) + break; + string username(static_cast((*found)->value().data), (*found)->value().length); + secdebug("AuthEvalMech", "found username"); + found = find_if(context.begin(), context.end(), FindAuthItemByRightName(kAuthorizationEnvironmentPassword) ); + if (found == context.end()) + break; + string password(static_cast((*found)->value().data), (*found)->value().length); + secdebug("AuthEvalMech", "found password"); + Credential newCredential(username, password, true); // create a new shared credential + + if (newCredential->isValid()) + { + Syslog::info("authinternal authenticated user %s (uid %lu).", newCredential->username().c_str(), newCredential->uid()); + return kAuthorizationResultAllow; + } + + Syslog::error("authinternal failed to authenticate user %s.", newCredential->username().c_str()); + + } while (0); + + return kAuthorizationResultDeny; +} + +/* +AuthItemSet & +AgentMechanismEvaluator::commonHints(const AuthorizationToken &auth) +{ + +} +*/ + +} /* namespace Authorization */ diff --git a/src/AuthorizationMechEval.h b/src/AuthorizationMechEval.h new file mode 100644 index 0000000..5a6375d --- /dev/null +++ b/src/AuthorizationMechEval.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + * AuthorizationMechEval.h + * securityd + * + */ + +#include +#include +#include +#include "agentquery.h" +#include "AuthorizationRule.h" +#include "authority.h" +#include "session.h" + + +namespace Authorization { + +class AgentMechanismRef : public RefPointer +{ +public: + AgentMechanismRef(uid_t clientUID, const Session &session); + AgentMechanismRef(); +}; + +class AgentMechanismEvaluator +{ +public: + AgentMechanismEvaluator(uid_t uid, const Session &session, const vector& inMechanisms); + OSStatus run(const AuthValueVector &arguments, const AuthItemSet &hints, const AuthItemSet &context); + + AuthorizationResult AgentMechanismEvaluator::authinternal(AuthItemSet &context); + + AuthItemSet &hints() { return mHints; } + AuthItemSet &context() { return mContext; } + +private: + vector mMechanisms; + typedef map ClientMap; + ClientMap mClients; + + uid_t mClientUid; + const Session &mSession; + + AuthItemSet mHints; + AuthItemSet mContext; +}; + +} /* namespace Authorization */ diff --git a/src/AuthorizationRule.cpp b/src/AuthorizationRule.cpp new file mode 100644 index 0000000..847ae60 --- /dev/null +++ b/src/AuthorizationRule.cpp @@ -0,0 +1,961 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + * AuthorizationRule.cpp + * Security + * + */ + +#include "AuthorizationRule.h" +#include +#include +#include +#include +#include "authority.h" +#include "server.h" +#include "process.h" +#include "agentquery.h" +#include "AuthorizationMechEval.h" + +#include +#include +#include + + +// +// Rule class +// +namespace Authorization { + +CFStringRef RuleImpl::kUserGroupID = CFSTR(kAuthorizationRuleParameterGroup); +CFStringRef RuleImpl::kTimeoutID = CFSTR(kAuthorizationRuleParameterCredentialTimeout); +CFStringRef RuleImpl::kSharedID = CFSTR(kAuthorizationRuleParameterCredentialShared); +CFStringRef RuleImpl::kAllowRootID = CFSTR(kAuthorizationRuleParameterAllowRoot); +CFStringRef RuleImpl::kMechanismsID = CFSTR(kAuthorizationRuleParameterMechanisms); +CFStringRef RuleImpl::kSessionOwnerID = CFSTR(kAuthorizationRuleParameterCredentialSessionOwner); +CFStringRef RuleImpl::kKofNID = CFSTR(kAuthorizationRuleParameterKofN); +CFStringRef RuleImpl::kPromptID = CFSTR(kAuthorizationRuleParameterDefaultPrompt); +CFStringRef RuleImpl::kTriesID = CFSTR("tries"); // XXX/cs move to AuthorizationTagsPriv.h + +CFStringRef RuleImpl::kRuleClassID = CFSTR(kAuthorizationRuleClass); +CFStringRef RuleImpl::kRuleAllowID = CFSTR(kAuthorizationRuleClassAllow); +CFStringRef RuleImpl::kRuleDenyID = CFSTR(kAuthorizationRuleClassDeny); +CFStringRef RuleImpl::kRuleUserID = CFSTR(kAuthorizationRuleClassUser); +CFStringRef RuleImpl::kRuleDelegateID = CFSTR(kAuthorizationRightRule); +CFStringRef RuleImpl::kRuleMechanismsID = CFSTR(kAuthorizationRuleClassMechanisms); + +string +RuleImpl::Attribute::getString(CFDictionaryRef config, CFStringRef key, bool required = false, char *defaultValue = NULL) +{ + CFTypeRef value = CFDictionaryGetValue(config, key); + if (value && (CFGetTypeID(value) == CFStringGetTypeID())) + { + CFStringRef stringValue = reinterpret_cast(value); + char buffer[512]; + const char *ptr = CFStringGetCStringPtr(stringValue, kCFStringEncodingUTF8); + if (ptr == NULL) + { + if (CFStringGetCString(stringValue, buffer, sizeof(buffer), kCFStringEncodingUTF8)) + ptr = buffer; + else + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + } + + return string(ptr); + } + else + if (!required) + return string(defaultValue); + else + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule +} + +double +RuleImpl::Attribute::getDouble(CFDictionaryRef config, CFStringRef key, bool required = false, double defaultValue = 0.0) +{ + double doubleValue = 0; + + CFTypeRef value = CFDictionaryGetValue(config, key); + if (value && (CFGetTypeID(value) == CFNumberGetTypeID())) + { + CFNumberGetValue(reinterpret_cast(value), kCFNumberDoubleType, &doubleValue); + } + else + if (!required) + return defaultValue; + else + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + + return doubleValue; +} + +bool +RuleImpl::Attribute::getBool(CFDictionaryRef config, CFStringRef key, bool required = false, bool defaultValue = false) +{ + bool boolValue = false; + CFTypeRef value = CFDictionaryGetValue(config, key); + + if (value && (CFGetTypeID(value) == CFBooleanGetTypeID())) + { + boolValue = CFBooleanGetValue(reinterpret_cast(value)); + } + else + if (!required) + return defaultValue; + else + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + + return boolValue; +} + +// add reference to string that we're modifying +void +RuleImpl::Attribute::setString(CFMutableDictionaryRef config, CFStringRef key, string &value) +{ + CFStringRef cfstringValue = CFStringCreateWithCString(NULL /*allocator*/, value.c_str(), kCFStringEncodingUTF8); + + if (cfstringValue) + { + CFDictionarySetValue(config, key, cfstringValue); + CFRelease(cfstringValue); + } + else + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid attribute +} + +void +RuleImpl::Attribute::setDouble(CFMutableDictionaryRef config, CFStringRef key, double value) +{ + CFNumberRef doubleValue = CFNumberCreate(NULL /*allocator*/, kCFNumberDoubleType, doubleValue); + + if (doubleValue) + { + CFDictionarySetValue(config, key, doubleValue); + CFRelease(doubleValue); + } + else + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid attribute +} + +void +RuleImpl::Attribute::setBool(CFMutableDictionaryRef config, CFStringRef key, bool value) +{ + if (value) + CFDictionarySetValue(config, key, kCFBooleanTrue); + else + CFDictionarySetValue(config, key, kCFBooleanFalse); +} + +vector +RuleImpl::Attribute::getVector(CFDictionaryRef config, CFStringRef key, bool required = false) +{ + vector valueArray; + + CFTypeRef value = CFDictionaryGetValue(config, key); + if (value && (CFGetTypeID(value) == CFArrayGetTypeID())) + { + CFArrayRef evalArray = reinterpret_cast(value); + + for (int index=0; index < CFArrayGetCount(evalArray); index++) + { + CFTypeRef arrayValue = CFArrayGetValueAtIndex(evalArray, index); + if (arrayValue && (CFGetTypeID(arrayValue) == CFStringGetTypeID())) + { + CFStringRef stringValue = reinterpret_cast(arrayValue); + char buffer[512]; + const char *ptr = CFStringGetCStringPtr(stringValue, kCFStringEncodingUTF8); + if (ptr == NULL) + { + if (CFStringGetCString(stringValue, buffer, sizeof(buffer), kCFStringEncodingUTF8)) + ptr = buffer; + else + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + } + valueArray.push_back(string(ptr)); + } + } + } + else + if (required) + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + + return valueArray; +} + + +bool RuleImpl::Attribute::getLocalizedPrompts(CFDictionaryRef config, map &localizedPrompts) +{ + CFIndex numberOfPrompts = 0; + CFDictionaryRef promptsDict; + if (CFDictionaryContainsKey(config, kPromptID)) + { + promptsDict = reinterpret_cast(CFDictionaryGetValue(config, kPromptID)); + if (promptsDict && (CFGetTypeID(promptsDict) == CFDictionaryGetTypeID())) + numberOfPrompts = CFDictionaryGetCount(promptsDict); + } + if (numberOfPrompts == 0) + return false; + + const void *keys[numberOfPrompts+1]; + const void *values[numberOfPrompts+1]; + CFDictionaryGetKeysAndValues(promptsDict, &keys[0], &values[0]); + + while (numberOfPrompts-- > 0) + { + CFStringRef keyRef = reinterpret_cast(keys[numberOfPrompts]); + CFStringRef valueRef = reinterpret_cast(values[numberOfPrompts]); + if (!keyRef || (CFGetTypeID(keyRef) != CFStringGetTypeID())) + continue; + if (!valueRef || (CFGetTypeID(valueRef) != CFStringGetTypeID())) + continue; + string key = cfString(keyRef); + string value = cfString(valueRef); + localizedPrompts[kAuthorizationRuleParameterDescription+key] = value; + } + + return true; +} + + +// default rule +RuleImpl::RuleImpl() : +mType(kUser), mGroupName("admin"), mMaxCredentialAge(300.0), mShared(true), mAllowRoot(false), mSessionOwner(false), mTries(0) +{ + // XXX/cs read default descriptions from somewhere + // @@@ Default rule is shared admin group with 5 minute timeout +} + +// return rule built from rule definition; throw if invalid. +RuleImpl::RuleImpl(const string &inRightName, CFDictionaryRef cfRight, CFDictionaryRef cfRules) : mRightName(inRightName) +{ + // @@@ make sure cfRight is non mutable and never used that way + + if (CFGetTypeID(cfRight) != CFDictionaryGetTypeID()) + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + + mTries = 0; + + string classTag = Attribute::getString(cfRight, kRuleClassID, false, ""); + + if (classTag.length()) + { + if (classTag == kAuthorizationRuleClassAllow) + { + secdebug("authrule", "%s : rule allow", inRightName.c_str()); + mType = kAllow; + } + else if (classTag == kAuthorizationRuleClassDeny) + { + secdebug("authrule", "%s : rule deny", inRightName.c_str()); + mType = kDeny; + } + else if (classTag == kAuthorizationRuleClassUser) + { + mType = kUser; + mGroupName = Attribute::getString(cfRight, kUserGroupID); + // grab other user-in-group attributes + mMaxCredentialAge = Attribute::getDouble(cfRight, kTimeoutID, false, DBL_MAX); + mShared = Attribute::getBool(cfRight, kSharedID); + mAllowRoot = Attribute::getBool(cfRight, kAllowRootID); + mSessionOwner = Attribute::getBool(cfRight, kSessionOwnerID); + // authorization tags can have eval now too + mEvalDef = Attribute::getVector(cfRight, kMechanismsID); + mTries = int(Attribute::getDouble(cfRight, kTriesID, false, 3.0)); // XXX/cs double(kAuthorizationMaxTries) + + secdebug("authrule", "%s : rule user in group \"%s\" timeout %g%s%s", + inRightName.c_str(), + mGroupName.c_str(), mMaxCredentialAge, mShared ? " shared" : "", + mAllowRoot ? " allow-root" : ""); + + } + else if (classTag == kAuthorizationRuleClassMechanisms) + { + secdebug("authrule", "%s : rule evaluate mechanisms", inRightName.c_str()); + mType = kEvaluateMechanisms; + // mechanisms to evaluate + mEvalDef = Attribute::getVector(cfRight, kMechanismsID, true); + mTries = int(Attribute::getDouble(cfRight, kTriesID, false, 0.0)); // "forever" + } + else if (classTag == kAuthorizationRightRule) + { + assert(cfRules); // this had better not be a rule + secdebug("authrule", "%s : rule delegate rule", inRightName.c_str()); + mType = kRuleDelegation; + + // string or + string ruleDefString = Attribute::getString(cfRight, kRuleDelegateID, false, ""); + if (ruleDefString.length()) + { + CFStringRef ruleDefRef = makeCFString(ruleDefString); + CFDictionaryRef cfRuleDef = reinterpret_cast(CFDictionaryGetValue(cfRules, ruleDefRef)); + if (ruleDefRef) + CFRelease(ruleDefRef); + if (!cfRuleDef || CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID()) + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + mRuleDef.push_back(Rule(ruleDefString, cfRuleDef, NULL)); + } + else // array + { + vector ruleDef = Attribute::getVector(cfRight, kRuleDelegateID, true); + for (vector::const_iterator it = ruleDef.begin(); it != ruleDef.end(); it++) + { + CFStringRef ruleNameRef = makeCFString(*it); + CFDictionaryRef cfRuleDef = reinterpret_cast(CFDictionaryGetValue(cfRules, ruleNameRef)); + if (ruleNameRef) + CFRelease(ruleNameRef); + if (!cfRuleDef || (CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID())) + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + mRuleDef.push_back(Rule(*it, cfRuleDef, NULL)); + } + } + + mKofN = int(Attribute::getDouble(cfRight, kKofNID, false, 0.0)); + if (mKofN) + mType = kKofN; + + } + else + { + secdebug("authrule", "%s : rule class unknown %s.", inRightName.c_str(), classTag.c_str()); + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + } + } + else + { + // no class tag means, this is the abbreviated specification from the API + // it _must_ have a definition for "rule" which will be used as a delegate + // it may have a comment (not extracted here) + // it may have a default prompt, or a whole dictionary of languages (not extracted here) + assert(cfRules); + mType = kRuleDelegation; + string ruleName = Attribute::getString(cfRight, kRuleDelegateID, true); + secdebug("authrule", "%s : rule delegate rule (1): %s", inRightName.c_str(), ruleName.c_str()); + CFStringRef ruleNameRef = makeCFString(ruleName); + CFDictionaryRef cfRuleDef = reinterpret_cast(CFDictionaryGetValue(cfRules, ruleNameRef)); + if (ruleNameRef) + CFRelease(ruleNameRef); + if (!cfRuleDef || CFGetTypeID(cfRuleDef) != CFDictionaryGetTypeID()) + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + mRuleDef.push_back(Rule(ruleName, cfRuleDef, NULL)); + } + + Attribute::getLocalizedPrompts(cfRight, mLocalizedPrompts); +} + +/* +RuleImpl::~Rule() +{ +} +*/ + +void +RuleImpl::setAgentHints(const AuthItemRef &inRight, const Rule &inTopLevelRule, AuthItemSet &environmentToClient, AuthorizationToken &auth) const +{ + string authorizeString(inRight->name()); + environmentToClient.insert(AuthItemRef(AGENT_HINT_AUTHORIZE_RIGHT, AuthValueOverlay(authorizeString))); + + // XXX/cs pid/uid/client should only be added when we're ready to call the agent + pid_t cPid = Server::process().pid(); + environmentToClient.insert(AuthItemRef(AGENT_HINT_CLIENT_PID, AuthValueOverlay(sizeof(pid_t), &cPid))); + + uid_t cUid = auth.creatorUid(); + environmentToClient.insert(AuthItemRef(AGENT_HINT_CLIENT_UID, AuthValueOverlay(sizeof(uid_t), &cUid))); + + pid_t creatorPid = auth.creatorPid(); + environmentToClient.insert(AuthItemRef(AGENT_HINT_CREATOR_PID, AuthValueOverlay(sizeof(pid_t), &creatorPid))); + + { + CodeSigning::OSXCode *osxcode = auth.creatorCode(); + if (!osxcode) + MacOSError::throwMe(errAuthorizationDenied); + + string encodedBundle = osxcode->encode(); + char bundleType = (encodedBundle.c_str())[0]; // yay, no accessor + string bundlePath = osxcode->canonicalPath(); + + environmentToClient.insert(AuthItemRef(AGENT_HINT_CLIENT_TYPE, AuthValueOverlay(sizeof(bundleType), &bundleType))); + environmentToClient.insert(AuthItemRef(AGENT_HINT_CLIENT_PATH, AuthValueOverlay(bundlePath))); + } + + map defaultPrompts = inTopLevelRule->localizedPrompts(); + + if (defaultPrompts.empty()) + defaultPrompts = localizedPrompts(); + + if (!defaultPrompts.empty()) + { + map::const_iterator it; + for (it = defaultPrompts.begin(); it != defaultPrompts.end(); it++) + { + const string &key = it->first; + const string &value = it->second; + environmentToClient.insert(AuthItemRef(key.c_str(), AuthValueOverlay(value))); + } + } + + // add rulename as a hint + string ruleName = name(); + environmentToClient.insert(AuthItemRef(AGENT_HINT_AUTHORIZE_RULE, AuthValueOverlay(ruleName))); +} + +string +RuleImpl::agentNameForAuth(const AuthorizationToken &auth) const +{ + uint8_t hash[20]; + AuthorizationBlob authBlob = auth.handle(); + CssmData hashedData = CssmData::wrap(&hash, sizeof(hash)); + CssmData data = CssmData::wrap(&authBlob, sizeof(authBlob)); + CssmClient::Digest ctx(Server::csp(), CSSM_ALGID_SHA1); + try { + ctx.digest(data, hashedData); + } + catch (CssmError &e) + { + secdebug("auth", "digesting authref failed (%lu)", e.osStatus()); + return string("SecurityAgentMechanism"); + } + + uint8_t *point = static_cast(hashedData.data()); + for (uint8_t i=0; i < hashedData.length(); point++, i++) + { + uint8 value = (*point % 62) + '0'; + if (value > '9') value += 7; + if (value > 'Z') value += 6; + *point = value; + } + return string(static_cast(hashedData.data()), hashedData.length()); +} + +OSStatus +RuleImpl::evaluateAuthorization(const AuthItemRef &inRight, const Rule &inRule, + AuthItemSet &environmentToClient, + AuthorizationFlags flags, CFAbsoluteTime now, + const CredentialSet *inCredentials, + CredentialSet &credentials, AuthorizationToken &auth) const +{ + OSStatus status = errAuthorizationDenied; + + string usernamehint; + evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, usernamehint); + if (usernamehint.length()) + environmentToClient.insert(AuthItemRef(AGENT_HINT_SUGGESTED_USER, AuthValueOverlay(usernamehint))); + + if ((mType == kUser) && (mGroupName.length())) + environmentToClient.insert(AuthItemRef(AGENT_HINT_REQUIRE_USER_IN_GROUP, AuthValueOverlay(mGroupName))); + + uint32 tries; + SecurityAgent::Reason reason = SecurityAgent::noReason; + + Process &cltProc = Server::process(); + // Authorization preserves creator's UID in setuid processes + uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid(); + secdebug("AuthEvalMech", "Mechanism invocation by process %d (UID %d)", cltProc.pid(), cltUid); + + AgentMechanismEvaluator eval(cltUid, cltProc.session(), mEvalDef); + + for (tries = 0; tries < mTries; tries++) + { + AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason)); + environmentToClient.erase(retryHint); environmentToClient.insert(retryHint); // replace + AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries)); + environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace + + status = eval.run(AuthValueVector(), environmentToClient, auth.infoSet()); + + if ((status == errAuthorizationSuccess) || + (status == errAuthorizationCanceled)) // @@@ can only pass back sideband through context + { + secdebug("AuthEvalMech", "storing new context for authorization"); + auth.setInfoSet(eval.context()); + } + + // successfully ran mechanisms to obtain credential + if (status == errAuthorizationSuccess) + { + // deny is the default + status = errAuthorizationDenied; + + // fetch context and construct a credential to be tested + AuthItemSet inContext = auth.infoSet(); + CredentialSet newCredentials = makeCredentials(inContext); + // clear context after extracting credentials + auth.clearInfoSet(); + + for (CredentialSet::const_iterator it = newCredentials.begin(); it != newCredentials.end(); ++it) + { + const Credential& newCredential = *it; + + // @@@ we log the uid a process was running under when it created the authref, which is misleading in the case of loginwindow + if (newCredential->isValid()) + Syslog::info("uid %lu succeeded authenticating as user %s (uid %lu) for right %s.", auth.creatorUid(), newCredential->username().c_str(), newCredential->uid(), inRight->name()); + else + // we can't be sure that the user actually exists so inhibit logging of uid + Syslog::error("uid %lu failed to authenticate as user %s for right %s.", auth.creatorUid(), newCredential->username().c_str(), inRight->name()); + + if (!newCredential->isValid()) + { + reason = SecurityAgent::invalidPassphrase; //invalidPassphrase; + continue; + } + + // verify that this credential authorizes right + status = evaluateCredentialForRight(inRight, inRule, environmentToClient, now, newCredential, true); + + if (status == errAuthorizationSuccess) + { + // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent + credentials.erase(newCredential); credentials.insert(newCredential); + // use valid credential to set context info + auth.setCredentialInfo(newCredential); + secdebug("SSevalMech", "added valid credential for user %s", newCredential->username().c_str()); + status = errAuthorizationSuccess; + break; + } + else + reason = SecurityAgent::userNotInGroup; //unacceptableUser; // userNotInGroup + } + + if (status == errAuthorizationSuccess) + break; + } + else + if ((status == errAuthorizationCanceled) || + (status == errAuthorizationInternal)) + { + auth.clearInfoSet(); + break; + } + } + + // If we fell out of the loop because of too many tries, notify user + if (tries == mTries) + { + reason = SecurityAgent::tooManyTries; + AuthItemRef retryHint (AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason)); + environmentToClient.erase(retryHint); environmentToClient.insert(retryHint); // replace + AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries)); + environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace + eval.run(AuthValueVector(), environmentToClient, auth.infoSet()); + // XXX/cs is this still necessary? + auth.clearInfoSet(); + } + + return status; +} + +// create externally verified credentials on the basis of +// mechanism-provided information +CredentialSet +RuleImpl::makeCredentials(const AuthItemSet &context) const +{ + CredentialSet newCredentials; + + do { + AuthItemSet::const_iterator found = find_if(context.begin(), context.end(), FindAuthItemByRightName(kAuthorizationEnvironmentUsername) ); + if (found == context.end()) + break; + string username = (**found).stringValue(); + secdebug("AuthEvalMech", "found username"); + + const uid_t *uid = NULL; + found = find_if(context.begin(), context.end(), FindAuthItemByRightName("uid") ); + if (found != context.end()) + { + uid = static_cast((**found).value().data); + secdebug("AuthEvalMech", "found uid"); + } + + const gid_t *gid = NULL; + found = find_if(context.begin(), context.end(), FindAuthItemByRightName("gid") ); + if (found != context.end()) + { + gid = static_cast((**found).value().data); + secdebug("AuthEvalMech", "found gid"); + } + + if (username.length() && uid && gid) + { + // credential is valid because mechanism says so + newCredentials.insert(Credential(username, *uid, *gid, mShared)); + } + else + { + found = find_if(context.begin(), context.end(), FindAuthItemByRightName(kAuthorizationEnvironmentPassword) ); + if (found != context.end()) + { + secdebug("AuthEvalMech", "found password"); + string password = (**found).stringValue(); + secdebug("AuthEvalMech", "falling back on username/password credential if valid"); + newCredentials.insert(Credential(username, password, mShared)); + } + } + } while(0); + + return newCredentials; +} + +// evaluate whether a good credential of the current session owner would authorize a right +OSStatus +RuleImpl::evaluateSessionOwner(const AuthItemRef &inRight, const Rule &inRule, + const AuthItemSet &environment, + const CFAbsoluteTime now, + const AuthorizationToken &auth, + string& usernamehint) const +{ + // username hint is taken from the user who created the authorization, unless it's clearly ineligible + OSStatus status = noErr; + // @@@ we have no access to current requester uid here and the process uid is only taken when the authorization is created + // meaning that a process like loginwindow that drops privs later is screwed. + uid_t uid = auth.creatorUid(); + + Server::active().longTermActivity(); + struct passwd *pw = getpwuid(uid); + if (pw != NULL) + { + // avoid hinting a locked account (ie. root) + if ( (pw->pw_passwd == NULL) || + strcmp(pw->pw_passwd, "*") ) { + // Check if username will authorize the request and set username to + // be used as a hint to the user if so + status = evaluateCredentialForRight(inRight, inRule, environment, now, Credential(pw->pw_name, pw->pw_uid, pw->pw_gid, mShared), true); + + if (status == errAuthorizationSuccess) + usernamehint = pw->pw_name; + } //fi + endpwent(); + } + return status; +} + + + +// Return errAuthorizationSuccess if this rule allows access based on the specified credential, +// return errAuthorizationDenied otherwise. +OSStatus +RuleImpl::evaluateCredentialForRight(const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, CFAbsoluteTime now, const Credential &credential, bool ignoreShared) const +{ + assert(mType == kUser); + + // Get the username from the credential + const char *user = credential->username().c_str(); + + // If the credential is not valid or it's age is more than the allowed maximum age + // for a credential, deny. + if (!credential->isValid()) + { + secdebug("autheval", "credential for user %s is invalid, denying right %s", user, inRight->name()); + return errAuthorizationDenied; + } + + if (now - credential->creationTime() > mMaxCredentialAge) + { + secdebug("autheval", "credential for user %s has expired, denying right %s", user, inRight->name()); + return errAuthorizationDenied; + } + + if (!ignoreShared && !mShared && credential->isShared()) + { + secdebug("autheval", "shared credential for user %s cannot be used, denying right %s", user, inRight->name()); + return errAuthorizationDenied; + } + + // A root (uid == 0) user can do anything + if (credential->uid() == 0) + { + secdebug("autheval", "user %s has uid 0, granting right %s", user, inRight->name()); + return errAuthorizationSuccess; + } + + // XXX/cs replace with remembered session-owner once that functionality is added to SecurityServer + if (mSessionOwner) + { + uid_t console_user; + struct stat console_stat; + if (!lstat("/dev/console", &console_stat)) + { + console_user = console_stat.st_uid; + if (credential->uid() == console_user) + { + secdebug("autheval", "user %s is session-owner(uid: %d), granting right %s", user, console_user, inRight->name()); + return errAuthorizationSuccess; + } + } + else + secdebug("autheval", "session-owner check failed."); + } + + if (mGroupName.length()) + { + const char *groupname = mGroupName.c_str(); + Server::active().longTermActivity(); + struct group *gr = getgrnam(groupname); + if (!gr) + return errAuthorizationDenied; + + // Is this the default group of this user? + // PR-2875126 declares gr_gid int, as opposed to advertised (getgrent(3)) gid_t + // When this is fixed this warning should go away. + if (credential->gid() == gr->gr_gid) + { + secdebug("autheval", "user %s has group %s(%d) as default group, granting right %s", + user, groupname, gr->gr_gid, inRight->name()); + endgrent(); + return errAuthorizationSuccess; + } + + for (char **group = gr->gr_mem; *group; ++group) + { + if (!strcmp(*group, user)) + { + secdebug("autheval", "user %s is a member of group %s, granting right %s", + user, groupname, inRight->name()); + endgrent(); + return errAuthorizationSuccess; + } + } + + secdebug("autheval", "user %s is not a member of group %s, denying right %s", + user, groupname, inRight->name()); + endgrent(); + } + + return errAuthorizationDenied; +} + + + +OSStatus +RuleImpl::evaluateUser(const AuthItemRef &inRight, const Rule &inRule, + AuthItemSet &environmentToClient, AuthorizationFlags flags, + CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, + AuthorizationToken &auth) const +{ + // If we got here, this is a kUser type rule, let's start looking for a + // credential that is satisfactory + + // Zeroth -- Here is an extra special saucy ugly hack to allow authorizations + // created by a proccess running as root to automatically get a right. + if (mAllowRoot && auth.creatorUid() == 0) + { + secdebug("autheval", "creator of authorization has uid == 0 granting right %s", + inRight->name()); + return errAuthorizationSuccess; + } + + // if this is a "is-admin" rule check that and return + // XXX/cs add way to specify is-admin class of rule: if (mNoVerify) + if (name() == kAuthorizationRuleIsAdmin) + { + string username; + if (!evaluateSessionOwner(inRight, inRule, environmentToClient, now, auth, username)) + return errAuthorizationSuccess; + } + + // First -- go though the credentials we either already used or obtained during this authorize operation. + for (CredentialSet::const_iterator it = credentials.begin(); it != credentials.end(); ++it) + { + OSStatus status = evaluateCredentialForRight(inRight, inRule, environmentToClient, now, *it, true); + if (status != errAuthorizationDenied) + { + // add credential to authinfo + auth.setCredentialInfo(*it); + return status; + } + } + + // Second -- go though the credentials passed in to this authorize operation by the state management layer. + if (inCredentials) + { + for (CredentialSet::const_iterator it = inCredentials->begin(); it != inCredentials->end(); ++it) + { + OSStatus status = evaluateCredentialForRight(inRight, inRule, environmentToClient, now, *it, false); + if (status == errAuthorizationSuccess) + { + // Add the credential we used to the output set. + // whack an equivalent credential, so it gets updated to a later achieved credential which must have been more stringent + credentials.erase(*it); credentials.insert(*it); + // add credential to authinfo + auth.setCredentialInfo(*it); + + return status; + } + else if (status != errAuthorizationDenied) + return status; + } + } + + // Finally -- We didn't find the credential in our passed in credential lists. Obtain a new credential if + // our flags let us do so. + if (!(flags & kAuthorizationFlagExtendRights)) + return errAuthorizationDenied; + + // authorizations that timeout immediately cannot be preauthorized + if ((flags & kAuthorizationFlagPreAuthorize) && + (mMaxCredentialAge == 0.0)) + { + inRight->setFlags(inRight->flags() | kAuthorizationFlagCanNotPreAuthorize); + return errAuthorizationSuccess; + } + + if (!(flags & kAuthorizationFlagInteractionAllowed)) + return errAuthorizationInteractionNotAllowed; + + setAgentHints(inRight, inRule, environmentToClient, auth); + + // If a different evaluation is prescribed, + // we'll run that and validate the credentials from there + // we fall back on a default configuration + return evaluateAuthorization(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); +} + +OSStatus +RuleImpl::evaluateMechanismOnly(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationToken &auth, CredentialSet &outCredentials) const +{ + uint32 tries = 0; + OSStatus status; + + Process &cltProc = Server::process(); + // Authorization preserves creator's UID in setuid processes + uid_t cltUid = (cltProc.uid() != 0) ? cltProc.uid() : auth.creatorUid(); + secdebug("AuthEvalMech", "Mechanism invocation by process %d (UID %d)", cltProc.pid(), cltUid); + + { + AgentMechanismEvaluator eval(cltUid, cltProc.session(), mEvalDef); + + do + { + setAgentHints(inRight, inRule, environmentToClient, auth); + AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(tries), &tries)); + environmentToClient.erase(triesHint); environmentToClient.insert(triesHint); // replace + + status = eval.run(AuthValueVector(), environmentToClient, auth.infoSet()); + + if ((status == errAuthorizationSuccess) || + (status == errAuthorizationCanceled)) // @@@ can only pass back sideband through context + { + secdebug("AuthEvalMech", "storing new context for authorization"); + auth.setInfoSet(eval.context()); + if (status == errAuthorizationSuccess) + { + outCredentials = makeCredentials(eval.context()); + } + } + + tries++; + } + while ((status == errAuthorizationDenied) // only if we have an expected failure we continue + && ((mTries == 0) // mTries == 0 means we try forever + || ((mTries > 0) // mTries > 0 means we try up to mTries times + && (tries < mTries)))); + } + + if (name() == "system.login.console") + { + QueryInvokeMechanism query(cltUid, cltProc.session()); + query.terminateAgent(); + } + + return status; +} + +OSStatus +RuleImpl::evaluateRules(const AuthItemRef &inRight, const Rule &inRule, + AuthItemSet &environmentToClient, AuthorizationFlags flags, + CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, + AuthorizationToken &auth) const +{ + // line up the rules to try + if (!mRuleDef.size()) + return errAuthorizationSuccess; + + uint32_t count = 0; + OSStatus status = errAuthorizationSuccess; + vector::const_iterator it; + + for (it = mRuleDef.begin();it != mRuleDef.end(); it++) + { + // are we at k yet? + if ((mType == kKofN) && (count == mKofN)) + return errAuthorizationSuccess; + + // get a rule and try it + status = (*it)->evaluate(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); + + // if status is cancel/internal error abort + if ((status == errAuthorizationCanceled) || (status == errAuthorizationInternal)) + return status; + + if (status != errAuthorizationSuccess) + { + // continue if we're only looking for k of n + if (mType == kKofN) + continue; + + break; + } + else + count++; + } + + return status; // return the last failure +} + + +OSStatus +RuleImpl::evaluate(const AuthItemRef &inRight, const Rule &inRule, + AuthItemSet &environmentToClient, AuthorizationFlags flags, + CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, + AuthorizationToken &auth) const +{ + switch (mType) + { + case kAllow: + secdebug("autheval", "rule is always allow"); + return errAuthorizationSuccess; + case kDeny: + secdebug("autheval", "rule is always deny"); + return errAuthorizationDenied; + case kUser: + secdebug("autheval", "rule is user"); + return evaluateUser(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); + case kRuleDelegation: + secdebug("autheval", "rule evaluates rules"); + return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); + case kKofN: + secdebug("autheval", "rule evaluates k-of-n rules"); + return evaluateRules(inRight, inRule, environmentToClient, flags, now, inCredentials, credentials, auth); + case kEvaluateMechanisms: + secdebug("autheval", "rule evaluates mechanisms"); + return evaluateMechanismOnly(inRight, inRule, environmentToClient, auth, credentials); + default: + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule + } +} + +Rule::Rule() : RefPointer(new RuleImpl()) {} +Rule::Rule(const string &inRightName, CFDictionaryRef cfRight, CFDictionaryRef cfRules) : RefPointer(new RuleImpl(inRightName, cfRight, cfRules)) {} + + + +} // end namespace Authorization diff --git a/src/AuthorizationRule.h b/src/AuthorizationRule.h new file mode 100644 index 0000000..21a4f20 --- /dev/null +++ b/src/AuthorizationRule.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + * + * AuthorizationRule.h + * Security + * + * Created by Conrad Sauerwald on Wed Mar 19 2003. + */ + +#ifndef _H_AUTHORIZATIONRULE +#define _H_AUTHORIZATIONRULE 1 + +#include +#include +#include "authority.h" + +namespace Authorization +{ + +class Rule; + +class RuleImpl : public RefCount +{ +public: + RuleImpl(); + RuleImpl(const string &inRightName, CFDictionaryRef cfRight, CFDictionaryRef cfRules); + + OSStatus evaluate(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, + AuthorizationFlags flags, CFAbsoluteTime now, + const CredentialSet *inCredentials, CredentialSet &credentials, + AuthorizationToken &auth) const; + + string name() const { return mRightName; } + +private: +// internal machinery + + // evaluate credential for right + OSStatus evaluateCredentialForRight(const AuthItemRef &inRight, const Rule &inRule, + const AuthItemSet &environment, + CFAbsoluteTime now, const Credential &credential, bool ignoreShared) const; + + + OSStatus evaluateRules(const AuthItemRef &inRight, const Rule &inRule, + AuthItemSet &environmentToClient, AuthorizationFlags flags, + CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, + AuthorizationToken &auth) const; + + void setAgentHints(const AuthItemRef &inRight, const Rule &inTopLevelRule, AuthItemSet &environmentToClient, AuthorizationToken &auth) const; + + // perform authorization based on running specified mechanisms (see evaluateMechanism) + OSStatus evaluateAuthorization(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth) const; + + OSStatus evaluateAuthorizationOld(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationFlags flags, CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, AuthorizationToken &auth) const; + + OSStatus evaluateUser(const AuthItemRef &inRight, const Rule &inRule, + AuthItemSet &environmentToClient, AuthorizationFlags flags, + CFAbsoluteTime now, const CredentialSet *inCredentials, CredentialSet &credentials, + AuthorizationToken &auth) const; + + OSStatus evaluateMechanismOnly(const AuthItemRef &inRight, const Rule &inRule, AuthItemSet &environmentToClient, AuthorizationToken &auth, CredentialSet &outCredentials) const; + + // find username hint based on session owner + OSStatus evaluateSessionOwner(const AuthItemRef &inRight, const Rule &inRule, const AuthItemSet &environment, const CFAbsoluteTime now, const AuthorizationToken &auth, string& usernamehint) const; + + string agentNameForAuth(const AuthorizationToken &auth) const; + CredentialSet makeCredentials(const AuthItemSet &context) const; + + map localizedPrompts() const { return mLocalizedPrompts; } + + +// parsed attributes +private: + enum Type + { + kDeny, + kAllow, + kUser, + kRuleDelegation, + kKofN, + kEvaluateMechanisms, + } mType; + + string mRightName; + string mGroupName; + CFTimeInterval mMaxCredentialAge; + bool mShared; + bool mAllowRoot; + vector mEvalDef; + bool mSessionOwner; + vector mRuleDef; + uint32_t mKofN; + mutable uint32_t mTries; + map mLocalizedPrompts; + +private: + + class Attribute + { + public: + static bool getBool(CFDictionaryRef config, CFStringRef key, bool required, bool defaultValue); + static double getDouble(CFDictionaryRef config, CFStringRef key, bool required, double defaultValue); + static string getString(CFDictionaryRef config, CFStringRef key, bool required, char *defaultValue); + static vector getVector(CFDictionaryRef config, CFStringRef key, bool required); + static void setString(CFMutableDictionaryRef config, CFStringRef key, string &value); + static void setDouble(CFMutableDictionaryRef config, CFStringRef key, double value); + static void setBool(CFMutableDictionaryRef config, CFStringRef key, bool value); + static bool getLocalizedPrompts(CFDictionaryRef config, map &localizedPrompts); + }; + + +// keys + static CFStringRef kUserGroupID; + static CFStringRef kTimeoutID; + static CFStringRef kSharedID; + static CFStringRef kAllowRootID; + static CFStringRef kMechanismsID; + static CFStringRef kSessionOwnerID; + static CFStringRef kKofNID; + static CFStringRef kPromptID; + static CFStringRef kTriesID; + + static CFStringRef kRuleClassID; + static CFStringRef kRuleAllowID; + static CFStringRef kRuleDenyID; + static CFStringRef kRuleUserID; + static CFStringRef kRuleDelegateID; + static CFStringRef kRuleMechanismsID; + +}; + +class Rule : public RefPointer +{ +public: + Rule(); + Rule(const string &inRightName, CFDictionaryRef cfRight, CFDictionaryRef cfRules); +}; + +}; /* namespace Authorization */ + +#endif /* ! _H_AUTHORIZATIONRULE */ diff --git a/src/acl_keychain.cpp b/src/acl_keychain.cpp new file mode 100644 index 0000000..2970217 --- /dev/null +++ b/src/acl_keychain.cpp @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// acl_keychain - a subject type for the protected-path +// keychain prompt interaction model. +// +// Arguments in CSSM_LIST form: +// list[1] = CssmData: CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure +// list[2] = CssmData: Descriptive String (presented to user in protected dialogs) +// For legacy compatibility, we accept a single-entry form +// list[1] = CssmData: Descriptive String +// which defaults to a particular CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR structure value. +// This is never produced by current code, and is considered purely a legacy feature. +// +// On-disk (flattened) representation: +// In order to accommodate legacy formats nicely, we use the binary-versioning feature +// of the ACL machinery. Version 0 is the legacy format (storing only the description +// string), while Version 1 contains both selector and description. To allow for +// maximum backward compatibility, legacy-compatible forms are written out as version 0. +// See isLegacyCompatible(). +// +// Some notes on Acl Update Triggers: +// When the user checks the "don't ask me again" checkbox in the access confirmation +// dialog, we respond by returning the informational error code +// CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT, and setting a count-down trigger +// in the connection. The caller is entitled to bypass our dialog (it succeeds +// automatically) within the next few (Connection::aclUpdateTriggerLimit == 3) +// requests, in order to update the object's ACL as requested. It must then retry +// the original access operation (which will presumably pass because of that edit). +// These are the rules: for the trigger to apply, the access must be to the same +// object, from the same connection, and within the next aclUpdateTriggerLimit accesses. +// (Currently, these are for a "get acl", "get owner", and the "change acl" calls.) +// Damage Control Department: The worst this mechanism could do, if subverted, is +// to bypass our confirmation dialog (making it appear to succeed to the ACL validation). +// But that is exactly what the "don't ask me again" checkbox is meant to do, so any +// subversion would be based on a (perhaps intentional) miscommunication between user +// and client process as to what the user consents not to be asked about (any more). +// The user can always examine the resulting ACL (in Keychain Access or elsewhere), and +// edit it to suit her needs. +// +#include "acl_keychain.h" +#include "agentquery.h" +#include "acls.h" +#include "connection.h" +#include "database.h" +#include "server.h" +#include +#include + + +#define ACCEPT_LEGACY_FORM 1 +#define FECKLESS_KEYCHAIN_ACCESS_EXCEPTION 1 + + +// +// The default for the selector structure. +// +CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR KeychainPromptAclSubject::defaultSelector = { + CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION, // version + 0 // flags +}; + + +// +// Validate a credential set against this subject. +// +bool KeychainPromptAclSubject::validate(const AclValidationContext &context, + const TypedList &sample) const +{ + if (SecurityServerEnvironment *env = context.environment()) { + // check for special ACL-update override + if (context.authorization() == CSSM_ACL_AUTHORIZATION_CHANGE_ACL + && Server::connection().aclWasSetForUpdateTrigger(env->acl)) { + secdebug("kcacl", "honoring acl update trigger for %p(%s)", + &env->acl, description.c_str()); + return true; + } + + // does the user need to type in the passphrase? + const Database *db = env->database(); + bool needPassphrase = db && (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE); + + // ask the user +#if FECKLESS_KEYCHAIN_ACCESS_EXCEPTION + Process &process = Server::process(); + secdebug("kcacl", "Keychain query from process %d (UID %d)", process.pid(), process.uid()); + if (process.clientCode()) + needPassphrase |= + process.clientCode()->canonicalPath() == "/Applications/Utilities/Keychain Access.app"; +#endif + QueryKeychainUse query(needPassphrase, db); + query.inferHints(Server::process()); + + if (query.queryUser(db ? db->dbName() : NULL, + description.c_str(), context.authorization()) + != SecurityAgent::noReason) + return false; + + // process "always allow..." response + if (query.remember) { + // mark for special ACL-update override (really soon) later + Server::connection().setAclUpdateTrigger(env->acl); + secdebug("kcacl", "setting acl update trigger for %p(%s)", + &env->acl, description.c_str()); + // fail with prejudice (caller will retry) + CssmError::throwMe(CSSMERR_CSP_APPLE_ADD_APPLICATION_ACL_SUBJECT); + } + + // finally, return the actual user response + return query.allow; + } + return false; // default to deny without prejudice +} + + +// +// Make a copy of this subject in CSSM_LIST form +// +CssmList KeychainPromptAclSubject::toList(Allocator &alloc) const +{ + // always issue new (non-legacy) form + return TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, + new(alloc) ListElement(alloc, CssmData::wrap(selector)), + new(alloc) ListElement(alloc, description)); +} + + +// +// Create a KeychainPromptAclSubject +// +KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(const TypedList &list) const +{ + switch (list.length()) { +#if ACCEPT_LEGACY_FORM + case 2: // legacy case: just description + { + ListElement *params[1]; + crack(list, 1, params, CSSM_LIST_ELEMENT_DATUM); + return new KeychainPromptAclSubject(*params[0], defaultSelector); + } +#endif //ACCEPT_LEGACY_FORM + case 3: // standard case: selector + description + { + ListElement *params[2]; + crack(list, 2, params, CSSM_LIST_ELEMENT_DATUM, CSSM_LIST_ELEMENT_DATUM); + return new KeychainPromptAclSubject(*params[1], + *params[0]->data().interpretedAs(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE)); + } + default: + CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); + } +} + +KeychainPromptAclSubject *KeychainPromptAclSubject::Maker::make(Version version, + Reader &pub, Reader &) const +{ + CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector; + const char *description; + switch (version) { + case pumaVersion: + selector = defaultSelector; + pub(description); + break; + case jaguarVersion: + pub(selector); + selector.version = n2h (selector.version); + selector.flags = n2h (selector.flags); + pub(description); + break; + } + return new KeychainPromptAclSubject(description, selector); +} + +KeychainPromptAclSubject::KeychainPromptAclSubject(string descr, + const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &sel) + : SimpleAclSubject(CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT), + selector(sel), description(descr) +{ + // check selector version + if (selector.version != CSSM_ACL_KEYCHAIN_PROMPT_CURRENT_VERSION) + CssmError::throwMe(CSSM_ERRCODE_INVALID_ACL_SUBJECT_VALUE); + + // determine binary compatibility version + if (selector.flags == 0) // compatible with old form + version(pumaVersion); + else + version(jaguarVersion); +} + + +// +// Export the subject to a memory blob +// +void KeychainPromptAclSubject::exportBlob(Writer::Counter &pub, Writer::Counter &priv) +{ + if (version() != 0) { + selector.version = h2n (selector.version); + selector.flags = h2n (selector.flags); + pub(selector); + } + + pub.insert(description.size() + 1); +} + +void KeychainPromptAclSubject::exportBlob(Writer &pub, Writer &priv) +{ + if (version() != 0) { + selector.version = h2n (selector.version); + selector.flags = h2n (selector.flags); + pub(selector); + } + pub(description.c_str()); +} + + +// +// Determine whether this ACL subject is in "legacy compatible" form. +// Legacy (<10.2) form contained no selector. +// +bool KeychainPromptAclSubject::isLegacyCompatible() const +{ + return selector.flags == 0; +} + + +#ifdef DEBUGDUMP + +void KeychainPromptAclSubject::debugDump() const +{ + Debug::dump("KeychainPrompt:%s(%s)", + description.c_str(), + (selector.flags & CSSM_ACL_KEYCHAIN_PROMPT_REQUIRE_PASSPHRASE) ? "passphrase" : "standard"); +} + +#endif //DEBUGDUMP diff --git a/src/acl_keychain.h b/src/acl_keychain.h new file mode 100644 index 0000000..764b931 --- /dev/null +++ b/src/acl_keychain.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// acl_keychain - a subject type for the protected-path +// keychain prompt interaction model. +// +#ifndef _ACL_KEYCHAIN +#define _ACL_KEYCHAIN + +#include +#include +#include + + +// +// This is the actual subject implementation class +// +class KeychainPromptAclSubject : public SimpleAclSubject { + static const Version pumaVersion = 0; // 10.0, 10.1 -> default selector (not stored) + static const Version jaguarVersion = 1; // 10.2 et al -> first version selector +public: + bool validate(const AclValidationContext &baseCtx, const TypedList &sample) const; + CssmList toList(Allocator &alloc) const; + + KeychainPromptAclSubject(string description, const CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR &selector); + + void exportBlob(Writer::Counter &pub, Writer::Counter &priv); + void exportBlob(Writer &pub, Writer &priv); + + IFDUMP(void debugDump() const); + + class Maker : public AclSubject::Maker { + public: + Maker(CSSM_ACL_SUBJECT_TYPE type = CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT) + : AclSubject::Maker(type) { } + KeychainPromptAclSubject *make(const TypedList &list) const; + KeychainPromptAclSubject *make(Version version, Reader &pub, Reader &priv) const; + }; + +private: + CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR selector; // selector structure + string description; // description blob (string) + +private: + static CSSM_ACL_KEYCHAIN_PROMPT_SELECTOR defaultSelector; + + typedef uint32 VersionMarker; + static const VersionMarker currentVersion = 0x3BD5910D; + + bool isLegacyCompatible() const; +}; + + +#endif //_ACL_KEYCHAIN diff --git a/src/acls.cpp b/src/acls.cpp new file mode 100644 index 0000000..8cfd5ba --- /dev/null +++ b/src/acls.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// acls - SecurityServer ACL implementation +// +#include "acls.h" +#include "connection.h" +#include "server.h" +#include +#include +#include +#include + + +// +// SecurityServerAcl is virtual +// +SecurityServerAcl::~SecurityServerAcl() +{ } + + +// +// Each SecurityServerAcl type must provide some indication of a database +// it is associated with. The default, naturally, is "none". +// +const Database *SecurityServerAcl::relatedDatabase() const +{ return NULL; } + + +// +// Provide environmental information to get/change-ACL calls. +// Also make them virtual so our children can override them. +// +void SecurityServerAcl::cssmChangeAcl(const AclEdit &edit, const AccessCredentials *cred) +{ + SecurityServerEnvironment env(*this); + ObjectAcl::cssmChangeAcl(edit, cred, &env); +} + +void SecurityServerAcl::cssmChangeOwner(const AclOwnerPrototype &newOwner, + const AccessCredentials *cred) +{ + SecurityServerEnvironment env(*this); + ObjectAcl::cssmChangeOwner(newOwner, cred, &env); +} + + +// +// Modified validate() methods to connect all the conduits... +// +void SecurityServerAcl::validate(AclAuthorization auth, const AccessCredentials *cred) +{ + SecurityServerEnvironment env(*this); + StLock objectSequence(aclSequence); + StLock processSequence(Server::process().aclSequence); + ObjectAcl::validate(auth, cred, &env); +} + +void SecurityServerAcl::validate(AclAuthorization auth, const Context &context) +{ + validate(auth, + context.get(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS)); +} + + +// +// Implement our environment object +// +uid_t SecurityServerEnvironment::getuid() const +{ + return Server::process().uid(); +} + +gid_t SecurityServerEnvironment::getgid() const +{ + return Server::process().gid(); +} + +pid_t SecurityServerEnvironment::getpid() const +{ + return Server::process().pid(); +} + +bool SecurityServerEnvironment::verifyCodeSignature(const CodeSigning::Signature *signature, + const CssmData *comment) +{ + return Server::codeSignatures().verify(Server::process(), signature, comment); +} diff --git a/src/acls.h b/src/acls.h new file mode 100644 index 0000000..9ea4504 --- /dev/null +++ b/src/acls.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2000-2001,2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// acls - SecurityServer ACL implementation +// +#ifndef _H_ACLS +#define _H_ACLS + +#include "securityserver.h" +#include +#include +#include + + +class Connection; +class Database; + + +// +// ACL implementation as used by the SecurityServer +// +class SecurityServerAcl : public ObjectAcl { +public: + SecurityServerAcl(AclKind k, Allocator &alloc) : ObjectAcl(alloc), mKind(k) { } + virtual ~SecurityServerAcl(); + + AclKind kind() const { return mKind; } + + // validation calls restated + void validate(AclAuthorization auth, const AccessCredentials *cred); + void validate(AclAuthorization auth, const Context &context); + + void cssmChangeAcl(const AclEdit &edit, const AccessCredentials *cred); + void cssmChangeOwner(const AclOwnerPrototype &newOwner, const AccessCredentials *cred); + + virtual const Database *relatedDatabase() const; + + // aclSequence is taken to serialize ACL validations to pick up mutual changes + Mutex aclSequence; + +private: + AclKind mKind; +}; + + +// +// Our implementation of an ACL validation environment uses information +// derived from a Connection object. It implements context for +// -- ProcessAclSubjects (getuid/getgid) +// -- KeychainPromptAclSubjects (connection link) +// +class SecurityServerEnvironment : public virtual AclValidationEnvironment, + public virtual ProcessAclSubject::Environment, + public virtual CodeSignatureAclSubject::Environment { +public: + SecurityServerEnvironment(const SecurityServerAcl &baseAcl) + : acl(baseAcl) { } + + const SecurityServerAcl &acl; + + const Database *database() const { return acl.relatedDatabase(); } + uid_t getuid() const; + gid_t getgid() const; + pid_t getpid() const; + bool verifyCodeSignature(const CodeSigning::Signature *signature, const CssmData *comment); +}; + + +#endif //_H_ACLS diff --git a/src/agentquery.cpp b/src/agentquery.cpp new file mode 100644 index 0000000..cb1d4aa --- /dev/null +++ b/src/agentquery.cpp @@ -0,0 +1,642 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +// +// passphrases - canonical code to obtain passphrases +// +#include "agentquery.h" +#include "authority.h" +#include "server.h" +#include "session.h" + +#include +#include + +// +// NOSA support functions. This is a test mode where the SecurityAgent +// is simulated via stdio in the client. Good for running automated tests +// of client programs. Only available if -DNOSA when compiling. +// +#if defined(NOSA) + +#include + +static void getNoSA(char *buffer, size_t bufferSize, const char *fmt, ...) +{ + // write prompt + va_list args; + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); + + // read reply + memset(buffer, 0, bufferSize); + const char *nosa = getenv("NOSA"); + if (!strcmp(nosa, "-")) { + if (fgets(buffer, bufferSize-1, stdin) == NULL) + CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION); + buffer[strlen(buffer)-1] = '\0'; // remove trailing newline + if (!isatty(fileno(stdin))) + printf("%s\n", buffer); // echo to output if input not terminal + } else { + strncpy(buffer, nosa, bufferSize-1); + printf("%s\n", buffer); + } + if (buffer[0] == '\0') // empty input -> cancellation + CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED); +} + +#endif //NOSA + + +// +// The default Mach service name for SecurityAgent +// +const char SecurityAgentQuery::defaultName[] = "com.apple.SecurityAgent"; + +using SecurityAgent::Reason; +using namespace Authorization; + +// +// Construct a query object +// + +SecurityAgentQuery::SecurityAgentQuery() : + SecurityAgent::Client(Server::process().uid(), + Server::session().bootstrapPort(), + defaultName), + mClientSession(Server::session()) +{ + secdebug("SecurityAgentQuery", "new query"); + + // XXX/cs set up the general settings for the client as hints + // any invoke will merge these and whatever the client passes on invoke + +} + +SecurityAgentQuery::SecurityAgentQuery(uid_t clientUID, + const Session &clientSession, + const char *agentName) : +SecurityAgent::Client(clientUID, clientSession.bootstrapPort(), agentName ? agentName : defaultName), + mClientSession(clientSession) +{ + secdebug("SecurityAgentQuery", "new query"); +} + +SecurityAgentQuery::~SecurityAgentQuery() +{ + secdebug("SecurityAgentQuery", "query dying"); + Server::connection().useAgent(NULL); + +#if defined(NOSA) + if (getenv("NOSA")) { + printf(" [query done]\n"); + return; + } +#endif + + if (state() != dead) + destroy(); +} + +void +SecurityAgentQuery::activate() +{ + if (isActive()) + return; + + // Before popping up an agent: is UI session allowed? + if (!(mClientSession.attributes() & sessionHasGraphicAccess)) + CssmError::throwMe(CSSM_ERRCODE_NO_USER_INTERACTION); + + // this may take a while + Server::active().longTermActivity(); + Server::connection().useAgent(this); + + try { + SecurityAgent::Client::activate(); + } catch (...) { + Server::connection().useAgent(NULL); // guess not + throw; + } + + +} + + +void +SecurityAgentQuery::inferHints(Process &thisProcess) +{ + AuthItemSet processHints = clientHints(thisProcess.clientCode(), thisProcess.pid(), thisProcess.uid()); + mClientHints.insert(processHints.begin(), processHints.end()); +} + +void +SecurityAgentQuery::readChoice() +{ + allow = false; + remember = false; + + AuthItem *allowAction = mContext.find(AGENT_CONTEXT_ALLOW); + if (allowAction) + { + string allowString; + if (allowAction->getString(allowString) + && (allowString == "YES")) + allow = true; + } + + AuthItem *rememberAction = mContext.find(AGENT_CONTEXT_REMEMBER_ACTION); + if (rememberAction) + { + string rememberString; + if (rememberAction->getString(rememberString) + && (rememberString == "YES")) + remember = true; + } +} + +void +SecurityAgentQuery::terminate() +{ + if (!isActive()) + activate(); + + // @@@ This happens already in the destructor; presumably we do this to tear things down orderly + Server::connection(true).useAgent(NULL); + + SecurityAgent::Client::terminate(); +} + + +// +// Perform the "rogue app" access query dialog +// +QueryKeychainUse::QueryKeychainUse(bool needPass, const Database *db) + : mPassphraseCheck(NULL) +{ + // if passphrase checking requested, save KeychainDatabase reference + // (will quietly disable check if db isn't a keychain) + if (needPass) + mPassphraseCheck = dynamic_cast(db); +} + +Reason QueryKeychainUse::queryUser (const char *database, const char *description, AclAuthorization action) +{ + Reason reason = SecurityAgent::noReason; + int retryCount = 0; + OSStatus status; + AuthValueVector arguments; + AuthItemSet hints, context; + +#if defined(NOSA) + if (getenv("NOSA")) { + char answer[maxPassphraseLength+10]; + + string applicationPath; + AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH); + if (applicationPathItem) + applicationPathItem->getString(applicationPath); + + getNoSA(answer, sizeof(answer), "Allow %s to do %d on %s in %s? [yn][g]%s ", + applicationPath.c_str(), int(action), (description ? description : "[NULL item]"), + (database ? database : "[NULL database]"), + mPassphraseCheck ? ":passphrase" : ""); + // turn passphrase (no ':') into y:passphrase + if (mPassphraseCheck && !strchr(answer, ':')) { + memmove(answer+2, answer, strlen(answer)+1); + memcpy(answer, "y:", 2); + } + + allow = answer[0] == 'y'; + remember = answer[1] == 'g'; + return SecurityAgent::noReason; + } +#endif + + activate(); + + // prepopulate with client hints + hints.insert(mClientHints.begin(), mClientHints.end()); + + // put action/operation (sint32) into hints + hints.insert(AuthItemRef(AGENT_HINT_ACL_TAG, AuthValueOverlay(sizeof(action), static_cast(&action)))); + + // item name into hints + + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_ITEM_NAME, AuthValueOverlay(description ? strlen(description) : 0, const_cast(description)))); + + // keychain name into hints + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database ? strlen(database) : 0, const_cast(database)))); + + + if (mPassphraseCheck) + { + status = create("builtin", "confirm-access-password", NULL); + + CssmAutoData data(Allocator::standard(Allocator::sensitive)); + + do + { + + AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount)); + hints.erase(triesHint); hints.insert(triesHint); // replace + + if (retryCount++ > kMaximumAuthorizationTries) + { + reason = SecurityAgent::tooManyTries; + } + + AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason)); + hints.erase(retryHint); hints.insert(retryHint); // replace + + status = invoke(arguments, hints, context); + + if (retryCount > kMaximumAuthorizationTries) + { + return reason; + } + + checkResult(); + + AuthItem *passwordItem = mContext.find(kAuthorizationEnvironmentPassword); + if (!passwordItem) + continue; + + passwordItem->getCssmData(data); + } + while (reason = (const_cast(mPassphraseCheck)->decode(data) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase)); + } + else + { + create("builtin", "confirm-access", NULL); + invoke(arguments, hints, context); + } + + readChoice(); + + return reason; +} + +// +// Perform code signature ACL access adjustment dialogs +// +bool QueryCodeCheck::operator () (const char *aclPath) +{ + OSStatus status; + AuthValueVector arguments; + AuthItemSet hints, context; + +#if defined(NOSA) + if (getenv("NOSA")) { + char answer[10]; + + string applicationPath; + AuthItem *applicationPathItem = mClientHints.find(AGENT_HINT_APPLICATION_PATH); + if (applicationPathItem) + applicationPathItem->getString(applicationPath); + + getNoSA(answer, sizeof(answer), + "Allow %s to match an ACL for %s [yn][g]? ", + applicationPath.c_str(), aclPath ? aclPath : "(unknown)"); + allow = answer[0] == 'y'; + remember = answer[1] == 'g'; + return; + } +#endif + + // prepopulate with client hints + hints.insert(mClientHints.begin(), mClientHints.end()); + + hints.insert(AuthItemRef(AGENT_HINT_APPLICATION_PATH, AuthValueOverlay(strlen(aclPath), const_cast(aclPath)))); + + MacOSError::check(create("builtin", "code-identity", NULL)); + + status = invoke(arguments, hints, context); + + checkResult(); + +// MacOSError::check(status); + + return kAuthorizationResultAllow == result(); +} + + +// +// Obtain passphrases and submit them to the accept() method until it is accepted +// or we can't get another passphrase. Accept() should consume the passphrase +// if it is accepted. If no passphrase is acceptable, throw out of here. +// +Reason QueryUnlock::query() +{ + Reason reason = SecurityAgent::noReason; + OSStatus status; + AuthValueVector arguments; + AuthItemSet hints, context; + CssmAutoData passphrase(Allocator::standard(Allocator::sensitive)); + int retryCount = 0; + +#if defined(NOSA) + // return the passphrase + if (getenv("NOSA")) { + char passphrase_[maxPassphraseLength]; + getNoSA(passphrase, maxPassphraseLength, "Unlock %s [ to cancel]: ", database.dbName()); + passphrase.copy(passphrase_, strlen(passphrase_)); + return database.decode(passphrase) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase; + } +#endif + activate(); + + + // prepopulate with client hints + + const char *keychainPath = database.dbName(); + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(strlen(keychainPath), const_cast(keychainPath)))); + + hints.insert(mClientHints.begin(), mClientHints.end()); + + MacOSError::check(create("builtin", "unlock-keychain", NULL)); + + do + { + AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount)); + hints.erase(triesHint); hints.insert(triesHint); // replace + + ++retryCount; + + if (retryCount > maxTries) + { + reason = SecurityAgent::tooManyTries; + } + + AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason)); + hints.erase(retryHint); hints.insert(retryHint); // replace + + status = invoke(arguments, hints, context); + + if (retryCount > maxTries) + { + return reason; + } + + checkResult(); + + AuthItem *passwordItem = mContext.find(kAuthorizationEnvironmentPassword); + if (!passwordItem) + continue; + + passwordItem->getCssmData(passphrase); + } + while (reason = database.decode(passphrase) ? SecurityAgent::noReason : SecurityAgent::invalidPassphrase); + + return SecurityAgent::noReason; +} + + +// +// Get existing passphrase (unlock) Query +// +Reason QueryUnlock::operator () () +{ + return query(); +} + + +// +// Obtain passphrases and submit them to the accept() method until it is accepted +// or we can't get another passphrase. Accept() should consume the passphrase +// if it is accepted. If no passphrase is acceptable, throw out of here. +// +Reason QueryNewPassphrase::query() +{ + Reason reason = initialReason; + CssmAutoData passphrase(Allocator::standard(Allocator::sensitive)); + CssmAutoData oldPassphrase(Allocator::standard(Allocator::sensitive)); + + OSStatus status; + AuthValueVector arguments; + AuthItemSet hints, context; + + int retryCount = 0; + +#if defined(NOSA) + if (getenv("NOSA")) { + char passphrase_[maxPassphraseLength]; + getNoSA(passphrase_, maxPassphraseLength, + "New passphrase for %s (reason %d) [ to cancel]: ", + database.dbName(), reason); + return SecurityAgent::noReason; + } +#endif + + activate(); + + // prepopulate with client hints + hints.insert(mClientHints.begin(), mClientHints.end()); + + // keychain name into hints + hints.insert(AuthItemRef(AGENT_HINT_KEYCHAIN_PATH, AuthValueOverlay(database.dbName()))); + + switch (initialReason) + { + case SecurityAgent::newDatabase: + MacOSError::check(create("builtin", "new-passphrase", NULL)); + break; + case SecurityAgent::changePassphrase: + MacOSError::check(create("builtin", "change-passphrase", NULL)); + break; + default: + assert(false); + } + + do + { + AuthItemRef triesHint(AGENT_HINT_TRIES, AuthValueOverlay(sizeof(retryCount), &retryCount)); + hints.erase(triesHint); hints.insert(triesHint); // replace + + if (++retryCount > maxTries) + { + reason = SecurityAgent::tooManyTries; + } + + AuthItemRef retryHint(AGENT_HINT_RETRY_REASON, AuthValueOverlay(sizeof(reason), &reason)); + hints.erase(retryHint); hints.insert(retryHint); // replace + + status = invoke(arguments, hints, context); + + if (retryCount > maxTries) + { + return reason; + } + + checkResult(); + + if (SecurityAgent::changePassphrase == initialReason) + { + AuthItem *oldPasswordItem = mContext.find(AGENT_PASSWORD); + if (!oldPasswordItem) + continue; + + oldPasswordItem->getCssmData(oldPassphrase); + } + + AuthItem *passwordItem = mContext.find(AGENT_CONTEXT_NEW_PASSWORD); + if (!passwordItem) + continue; + + passwordItem->getCssmData(passphrase); + + } + while (reason = accept(passphrase, (initialReason == SecurityAgent::changePassphrase) ? &oldPassphrase.get() : NULL)); + + return SecurityAgent::noReason; +} + + +// +// Get new passphrase Query +// +Reason QueryNewPassphrase::operator () (CssmOwnedData &passphrase) +{ + if (Reason result = query()) + return result; // failed + passphrase = mPassphrase; + return SecurityAgent::noReason; // success +} + +Reason QueryNewPassphrase::accept(CssmManagedData &passphrase, CssmData *oldPassphrase) +{ + //@@@ acceptance criteria are currently hardwired here + //@@@ This validation presumes ASCII - UTF8 might be more lenient + + // if we have an old passphrase, check it + if (oldPassphrase && !database.validatePassphrase(*oldPassphrase)) + return SecurityAgent::oldPassphraseWrong; + + // sanity check the new passphrase (but allow user override) + if (!(mPassphraseValid && passphrase.get() == mPassphrase)) { + mPassphrase = passphrase; + mPassphraseValid = true; + if (mPassphrase.length() == 0) + return SecurityAgent::passphraseIsNull; + if (mPassphrase.length() < 6) + return SecurityAgent::passphraseTooSimple; + } + + // accept this + return SecurityAgent::noReason; +} + +// +// Get a passphrase for unspecified use +// +Reason QueryGenericPassphrase::operator () (const char *prompt, bool verify, + string &passphrase) +{ + return query(prompt, verify, passphrase); +} + +Reason QueryGenericPassphrase::query(const char *prompt, bool verify, + string &passphrase) +{ + Reason reason = SecurityAgent::noReason; + OSStatus status; // not really used; remove? + AuthValueVector arguments; + AuthItemSet hints, context; + +#if defined(NOSA) + if (getenv("NOSA")) { + // FIXME 3690984 + return SecurityAgent::noReason; + } +#endif + + activate(); + + hints.insert(mClientHints.begin(), mClientHints.end()); + hints.insert(AuthItemRef(AGENT_HINT_CUSTOM_PROMPT, AuthValueOverlay(prompt ? strlen(prompt) : 0, const_cast(prompt)))); + // XXX/gh defined by dmitch but no analogous hint in + // AuthorizationTagsPriv.h: + // CSSM_ATTRIBUTE_ALERT_TITLE (optional alert panel title) + + if (false == verify) { // import + MacOSError::check(create("builtin", "generic-unlock", NULL)); + } else { // verify passphrase (export) + // new-passphrase-generic works with the pre-4 June 2004 agent; + // generic-new-passphrase is required for the new agent + MacOSError::check(create("builtin", "generic-new-passphrase", NULL)); + } + + AuthItem *passwordItem; + + do { + + status = invoke(arguments, hints, context); + checkResult(); + passwordItem = mContext.find(AGENT_PASSWORD); + + } while (!passwordItem); + + passwordItem->getString(passphrase); + + return reason; +} + + +QueryInvokeMechanism::QueryInvokeMechanism() : + SecurityAgentQuery() { } + +QueryInvokeMechanism::QueryInvokeMechanism(uid_t clientUID, const Session &session, const char *agentName) : + SecurityAgentQuery(clientUID, session, agentName) +{ +} + +void QueryInvokeMechanism::initialize(const string &inPluginId, const string &inMechanismId, const SessionId inSessionId) +{ + activate(); + + if (init == state()) + MacOSError::check(create(inPluginId.c_str(), inMechanismId.c_str(), inSessionId)); +} + +// XXX/cs should return AuthorizationResult +void QueryInvokeMechanism::run(const AuthValueVector &inArguments, AuthItemSet &inHints, AuthItemSet &inContext, AuthorizationResult *outResult) +{ + // prepopulate with client hints + inHints.insert(mClientHints.begin(), mClientHints.end()); + + MacOSError::check(invoke(inArguments, inHints, inContext)); + + if (outResult) *outResult = result(); + + inHints = hints(); + inContext = context(); +} + +void QueryInvokeMechanism::terminateAgent() +{ + terminate(); +} + diff --git a/src/agentquery.h b/src/agentquery.h new file mode 100644 index 0000000..16a34ad --- /dev/null +++ b/src/agentquery.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// passphrases - canonical code to obtain passphrases +// +#ifndef _H_AGENTQUERY +#define _H_AGENTQUERY + +#include "securityserver.h" +#include +#include +#include +#include "kcdatabase.h" +#include "AuthorizationEngine.h" + +using Authorization::AuthItemSet; +using Authorization::AuthValueVector; +using Security::CodeSigning::OSXCode; +// +// The common machinery of retryable SecurityAgent queries +// +class Session; + +class SecurityAgentQuery : protected SecurityAgent::Client { +public: + typedef SecurityAgent::Reason Reason; + + static const char defaultName[]; + + SecurityAgentQuery(); + SecurityAgentQuery(uid_t clientUID, const Session &clientSession, const char *agentName = defaultName); + + void inferHints(Process &thisProcess); + + virtual ~SecurityAgentQuery(); + + virtual void activate(); + virtual void terminate(); + +public: + void readChoice(); + + bool allow; + bool remember; + +protected: + AuthItemSet mClientHints; +private: + const Session &mClientSession; +}; + +// +// Specialized for "rogue app" alert queries +// +class QueryKeychainUse : public SecurityAgentQuery { +public: + QueryKeychainUse(bool needPass, const Database *db); + Reason queryUser (const char* database, const char *description, AclAuthorization action); + +private: + const KeychainDatabase *mPassphraseCheck; // NULL to not check passphrase +}; + + +// +// Specialized for code signature adjustment queries +// +class QueryCodeCheck : public SecurityAgentQuery { +public: + bool operator () (const char *aclPath); +}; + + +// +// A query for an existing passphrase +// +class QueryUnlock : public SecurityAgentQuery { + static const int maxTries = kMaximumAuthorizationTries; +public: + QueryUnlock(KeychainDatabase &db) : database(db) { } + + KeychainDatabase &database; + + Reason operator () (); + +protected: + Reason query(); + void queryInteractive(CssmOwnedData &passphrase); + void retryInteractive(CssmOwnedData &passphrase, Reason reason); + Reason accept(CssmManagedData &passphrase); +}; + + +// +// A query for a new passphrase +// +class QueryNewPassphrase : public SecurityAgentQuery { + static const int maxTries = 7; +public: + QueryNewPassphrase(KeychainDatabase &db, Reason reason) : + database(db), initialReason(reason), + mPassphrase(Allocator::standard(Allocator::sensitive)), + mPassphraseValid(false) { } + + KeychainDatabase &database; + + Reason operator () (CssmOwnedData &passphrase); + +protected: + Reason query(); + Reason accept(CssmManagedData &passphrase, CssmData *oldPassphrase); + +private: + Reason initialReason; + CssmAutoData mPassphrase; + bool mPassphraseValid; +}; + + +// +// Generic passphrase query (not associated with a database) +// +class QueryGenericPassphrase : public SecurityAgentQuery { +public: + QueryGenericPassphrase() { } + Reason operator () (const char *prompt, bool verify, + string &passphrase); + +protected: + Reason query(const char *prompt, bool verify, string &passphrase); +}; + + +class QueryInvokeMechanism : public RefCount, SecurityAgentQuery { +public: + QueryInvokeMechanism(); + QueryInvokeMechanism(uid_t clientUID, const Session &session, const char *agentName = NULL); + void initialize(const string &inPluginId, const string &inMechanismId, const SessionId inSessionId = 0); + void run(const AuthValueVector &inArguments, AuthItemSet &inHints, AuthItemSet &inContext, AuthorizationResult *outResult); + + bool operator () (const string &inPluginId, const string &inMechanismId, const Authorization::AuthValueVector &inArguments, AuthItemSet &inHints, AuthItemSet &inContext, AuthorizationResult *outResult); + void terminateAgent(); + //~QueryInvokeMechanism(); +}; + +#endif //_H_AGENTQUERY diff --git a/src/authority.cpp b/src/authority.cpp new file mode 100644 index 0000000..f9b0f3e --- /dev/null +++ b/src/authority.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// authority - authorization manager +// +#include "authority.h" +#include "server.h" +#include "connection.h" +#include "session.h" +#include "process.h" + +#include + +using Authorization::AuthItemSet; +using Authorization::AuthItemRef; +using Authorization::AuthValue; +using Authorization::AuthValueOverlay; + +// +// The global dictionary of extant AuthorizationTokens +// +AuthorizationToken::AuthMap AuthorizationToken::authMap; // set of extant authorizations +Mutex AuthorizationToken::authMapLock; // lock for mAuthorizations (only) + + + +// +// Create an authorization token. +// +AuthorizationToken::AuthorizationToken(Session &ssn, const CredentialSet &base, const security_token_t &securityToken) + : mBaseCreds(base), mTransferCount(INT_MAX), + mCreatorUid(securityToken.val[0]), + mCreatorCode(Server::process().clientCode()), + mCreatorPid(Server::process().pid()) +{ + // link to session + referent(ssn); + + // generate our (random) handle + Server::active().random(mHandle); + + // register handle in the global map + StLock _(authMapLock); + authMap[mHandle] = this; + + // all ready + secdebug("SSauth", "Authorization %p created using %d credentials; owner=%s", + this, int(mBaseCreds.size()), + mCreatorCode ? mCreatorCode->encode().c_str() : "unknown"); +} + +AuthorizationToken::~AuthorizationToken() +{ + // we better be clean + assert(mUsingProcesses.empty()); + + secdebug("SSauth", "Authorization %p destroyed", this); +} + + +Session &AuthorizationToken::session() const +{ + return referent(); +} + + +// +// Locate an authorization given its blob. +// +AuthorizationToken &AuthorizationToken::find(const AuthorizationBlob &blob) +{ + StLock _(authMapLock); + AuthMap::iterator it = authMap.find(blob); + if (it == authMap.end()) + Authorization::Error::throwMe(errAuthorizationInvalidRef); + return *it->second; +} + + +// +// Handle atomic deletion of AuthorizationToken objects +// +AuthorizationToken::Deleter::Deleter(const AuthorizationBlob &blob) + : lock(authMapLock) +{ + AuthMap::iterator it = authMap.find(blob); + if (it == authMap.end()) + Authorization::Error::throwMe(errAuthorizationInvalidRef); + mAuth = it->second; +} + +void AuthorizationToken::Deleter::remove() +{ + if (mAuth) { + authMap.erase(mAuth->handle()); + mAuth = NULL; + } +} + + +// +// Given a set of credentials, add it to our private credentials and return the result +// +// must hold Session::mCredsLock +CredentialSet AuthorizationToken::effectiveCreds() const +{ + secdebug("SSauth", "Authorization %p grabbing session %p creds %p", + this, &session(), &session().authCredentials()); + CredentialSet result = session().authCredentials(); + for (CredentialSet::const_iterator it = mBaseCreds.begin(); it != mBaseCreds.end(); it++) + if (!(*it)->isShared()) + result.insert(*it); + return result; +} + + +// +// Add more credential dependencies to an authorization +// +// must hold Session::mCredsLock +void AuthorizationToken::mergeCredentials(const CredentialSet &add) +{ + secdebug("SSauth", "Authorization %p merge creds %p", this, &add); + for (CredentialSet::const_iterator it = add.begin(); it != add.end(); it++) { + mBaseCreds.erase(*it); + mBaseCreds.insert(*it); + } + secdebug("SSauth", "Authorization %p merged %d new credentials for %d total", + this, int(add.size()), int(mBaseCreds.size())); +} + + +// +// Register a new process that uses this authorization token. +// This is an idempotent operation. +// +void AuthorizationToken::addProcess(Process &proc) +{ + StLock _(mLock); + mUsingProcesses.insert(&proc); + secdebug("SSauth", "Authorization %p added process %p(%d)", this, &proc, proc.pid()); +} + + +// +// Completely unregister client process. +// It does not matter how often it was registered with addProcess before. +// This returns true if no more processes use this token. Presumably you +// would then want to clean up, though that's up to you. +// +bool AuthorizationToken::endProcess(Process &proc) +{ + StLock _(mLock); + assert(mUsingProcesses.find(&proc) != mUsingProcesses.end()); + mUsingProcesses.erase(&proc); + secdebug("SSauth", "Authorization %p removed process %p(%d)%s", + this, &proc, proc.pid(), mUsingProcesses.empty() ? " FINAL" : ""); + return mUsingProcesses.empty(); +} + + +// +// Check whether internalization/externalization is allowed +// +bool AuthorizationToken::mayExternalize(Process &) const +{ + return mTransferCount > 0; +} + +bool AuthorizationToken::mayInternalize(Process &, bool countIt) +{ + StLock _(mLock); + if (mTransferCount > 0) { + if (countIt) { + mTransferCount--; + secdebug("SSauth", "Authorization %p decrement intcount to %d", this, mTransferCount); + } + return true; + } + return false; +} + +AuthItemSet +AuthorizationToken::infoSet(AuthorizationString tag) +{ + StLock _(mLock); // consider a separate lock + + AuthItemSet tempSet; + + if (tag) + { + AuthItemSet::iterator found = find_if(mInfoSet.begin(), mInfoSet.end(), + Authorization::FindAuthItemByRightName(tag)); + if (found != mInfoSet.end()) + tempSet.insert(AuthItemRef(*found)); + + } + else + tempSet = mInfoSet; + + secdebug("SSauth", "Authorization %p returning copy of context %s%s.", this, tag ? "for tag " : "", tag ? "" : tag); + + return tempSet; +} + +void +AuthorizationToken::setInfoSet(AuthItemSet &newInfoSet) +{ + StLock _(mLock); // consider a separate lock + secdebug("SSauth", "Authorization %p setting new context", this); + mInfoSet = newInfoSet; +} + +// This is destructive (non-merging) +void +AuthorizationToken::setCredentialInfo(const Credential &inCred) +{ + AuthItemSet dstInfoSet; + char uid_string[16]; // fit a uid_t(u_int32_t) + + if (snprintf(uid_string, sizeof(uid_string), "%u", inCred->uid()) >= + int(sizeof(uid_string))) + uid_string[0] = '\0'; + AuthItemRef uidHint("uid", AuthValueOverlay(uid_string ? strlen(uid_string) + 1 : 0, uid_string), 0); + dstInfoSet.insert(uidHint); + + AuthItemRef userHint("username", AuthValueOverlay(inCred->username()), 0); + dstInfoSet.insert(userHint); + + setInfoSet(dstInfoSet); +} + +void +AuthorizationToken::clearInfoSet() +{ + AuthItemSet dstInfoSet; + secdebug("SSauth", "Authorization %p clearing context", this); + setInfoSet(dstInfoSet); +} + diff --git a/src/authority.h b/src/authority.h new file mode 100644 index 0000000..4b2200c --- /dev/null +++ b/src/authority.h @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// authority - authorization manager +// +#ifndef _H_AUTHORITY +#define _H_AUTHORITY + +#include "securityserver.h" +#include +#include +#include "database.h" + +using Authorization::Credential; +using Authorization::CredentialSet; +using Authorization::AuthItemSet; + +class Process; +class Session; + +class AuthorizationToken : public PerSession { +public: + AuthorizationToken(Session &ssn, const CredentialSet &base, const security_token_t &securityToken); + ~AuthorizationToken(); + + Session &session() const; + + const AuthorizationBlob &handle() const { return mHandle; } + const CredentialSet &baseCreds() const { return mBaseCreds; } + CredentialSet effectiveCreds() const; + + typedef CredentialSet::iterator iterator; + iterator begin() { return mBaseCreds.begin(); } + iterator end() { return mBaseCreds.end(); } + + // add more credential dependencies + void mergeCredentials(const CredentialSet &more); + + // maintain process-owning links + void addProcess(Process &proc); + bool endProcess(Process &proc); + + // access control for external representations + bool mayExternalize(Process &proc) const; + bool mayInternalize(Process &proc, bool countIt = true); + + uid_t creatorUid() const { return mCreatorUid; } + CodeSigning::OSXCode *creatorCode() const { return mCreatorCode; } + pid_t creatorPid() const { return mCreatorPid; } + + AuthItemSet infoSet(AuthorizationString tag = NULL); + void setInfoSet(AuthItemSet &newInfoSet); + void setCredentialInfo(const Credential &inCred); + void clearInfoSet(); + +public: + static AuthorizationToken &find(const AuthorizationBlob &blob); + + class Deleter { + public: + Deleter(const AuthorizationBlob &blob); + + void remove(); + operator AuthorizationToken &() const { return *mAuth; } + + private: + AuthorizationToken *mAuth; + StLock lock; + }; + +private: + Mutex mLock; // object lock + AuthorizationBlob mHandle; // official randomized blob marker + CredentialSet mBaseCreds; // credentials we're based on + + unsigned int mTransferCount; // number of internalizations remaining + + typedef set ProcessSet; + ProcessSet mUsingProcesses; // set of process objects using this token + + uid_t mCreatorUid; // Uid of proccess that created this authorization + RefPointer mCreatorCode; // code id of creator + pid_t mCreatorPid; // Pid of processs that created this authorization + + AuthItemSet mInfoSet; // Side band info gathered from evaluations in this session + +private: + typedef map > AuthMap; + static AuthMap authMap; // set of extant authorizations + static Mutex authMapLock; // lock for mAuthorizations (only) +}; + + + + +#endif //_H_AUTHORITY diff --git a/src/child.cpp b/src/child.cpp new file mode 100644 index 0000000..27b9deb --- /dev/null +++ b/src/child.cpp @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// child - track a single child process and its belongings +// +#include "child.h" +#include +#include +#include + +kern_return_t +ucsp_server_handleSignal(mach_port_t sport, + mach_port_t task_port, + int signal_number) +{ + try { + if (task_port != mach_task_self()) { + Syslog::error("handleSignal: recieved from someone other than myself"); + } else { + ChildManager::childManager.handleSignal(signal_number); + } + } catch(...) {} + mach_port_deallocate(mach_task_self(), task_port); + + return 0; +} + +kern_return_t +ucsp_server_registerChild(mach_port_t sport, + mach_port_t rport, + mach_port_t task_port) +{ + mach_port_t childPort = rport; + try { + pid_t pid; + kern_return_t kt = pid_for_task(task_port, &pid); + if (kt) { + Syslog::error("registerChild: pid_for_task returned: %d", kt); + } else { + ChildManager::childManager.registerChild(pid, childPort); + } + } catch(...) {} + + if (childPort) { + // Dealloc the childPort unless registerChild set it to zero, which indicates it took over ownership. + mach_port_deallocate(mach_task_self(), childPort); + } + + if (task_port) + mach_port_deallocate(mach_task_self(), task_port); + + return 0; +} + + +// +// A Child object represents a UNIX process that was forked by us +// and may have some state associated with it. +// Although some children may be created per session (such as SecurityAgent instances) +// in general child processes are PerGlobal. +// +class Child +{ +public: + Child(pid_t pid); + ~Child(); + + void waitStatus(int status); + int waitStatus() const { return mStatus; } + + void childPort(mach_port_t &childPort); + + // Return our childPort and transfer ownership of it. + mach_port_t childPort() { mach_port_t childPort = mChildPort; mChildPort = 0; return childPort; } + + void eraseFromMap(); + void insertInMap(); + + // Wait for the child to register or die. If it registered mChildPort will be non zero, it will be 0 if it didn't. + void waitForRegister(); + +private: + Mutex mLockInternal; + StLock mLock; + pid_t mPid; + + // Service port for this child. + // This should only be nonzero while this object owns the port. Once it is retrieved ownership is handed off. + mach_port_t mChildPort; + + int mStatus; // Exit status if child died. + bool mInMap; +}; + + + +// +// Construct a Child object. +// +Child::Child(pid_t pid) : + mLock(mLockInternal), mPid(pid), mChildPort(0), mStatus(0), mInMap(false) +{ +} + +Child::~Child() +{ + try { + eraseFromMap(); + if (mChildPort) { + // Dealloc our mChildPort someone else took over ownership. + mach_port_deallocate(mach_task_self(), mChildPort); + } + } catch (...) {} +} + +void +Child::eraseFromMap() +{ + if (mInMap) + ChildManager::childManager.eraseChild(mPid); +} + +void +Child::insertInMap() +{ + ChildManager::childManager.insertChild(mPid, this); + mInMap = true; +} + +void +Child::waitStatus(int status) +{ + mStatus = status; + mLock.unlock(); // Unlock the lock to unblock waitForRegister() +} + +void +Child::childPort(mach_port_t &childPort) +{ + mChildPort = childPort; + childPort = 0; // Tell our caller that we consumed the child port. + + mLock.unlock(); // Unlock the lock to unblock waitForRegister() +} + +void +Child::waitForRegister() +{ + // Try to lock the lock again (this won't succeed until we get a register or wait result). + mLock.lock(); + // Unlock as soon as we get the lock. + mLock.unlock(); +} + + +// +// ChildManager - Singleton Child Manager class +// + +// The signleton ChildManager. +ChildManager ChildManager::childManager; + +ChildManager::ChildManager() +{ +} + +ChildManager::~ChildManager() +{ +} + +bool +ChildManager::forkChild(mach_port_t &outChildPort, int &outWaitStatus) +{ + StLock lock(mLock, false); + pid_t pid; + + /* Retry fork 10 times on failure after that just give up. */ + for (int tries = 0; tries < 10; ++tries) + { + lock.lock(); // aquire the lock + pid = fork(); + if (pid != pid_t(-1)) + break; + + /* Something went wrong. */ + lock.unlock(); // Release the lock so we aren't holding it during the usleep below. + + int err = errno; + if (err == EINTR) + continue; + + if (err == EAGAIN || err == ENOMEM) + usleep(100 * tries); + else + UnixError::throwMe(err); + } + + if (pid == 0) + { + // Child - return true. + return true; + } + else + { + Child child(pid); + child.insertInMap(); // Insert child into the map. + lock.unlock(); // Unlock as soon as we are done accessing mChildMap. + + child.waitForRegister(); + + // Remove child from the map. + child.eraseFromMap(); + + // Transfer ownership of child's childPort to our caller. + outChildPort = child.childPort(); + + if (!outChildPort) + outWaitStatus = child.waitStatus(); + + // Parent - return false + return false; + } +} + +void +ChildManager::handleSignal(int signal_number) +{ + if (signal_number == SIGCHLD) + handleSigChild(); +} + +void +ChildManager::registerChild(pid_t pid, mach_port_t &childPort) +{ + StLock _(mLock); + Child *child = findChild(pid); + if (child) + child->childPort(childPort); +} + +// Assumes mLock is not locked. +void +ChildManager::eraseChild(pid_t pid) +{ + StLock _(mLock); + mChildMap.erase(pid); +} + +// Assumes mLock is already locked. +void +ChildManager::insertChild(pid_t pid, Child *child) +{ + mChildMap[pid] = child; +} + + +// +// Private functions +// + +Child * +ChildManager::findChild(pid_t pid) +{ + ChildMap::iterator it = mChildMap.find(pid); + if (it == mChildMap.end()) + return NULL; + + return it->second; +} + +void +ChildManager::handleSigChild() +{ + for (int tries = 0; tries < 10; ++tries) + { + int status; + pid_t pid = waitpid(-1, &status, WNOHANG|WUNTRACED); + switch (pid) + { + case 0: + if (tries == 0) + Syslog::notice("Spurious SIGCHLD ignored"); + break; + case -1: + { + int err = errno; + if (err == ECHILD) + { + if (tries == 0) + Syslog::notice("Spurious SIGCHLD ignored"); + break; + } + + if (err != EINTR) + { + Syslog::error("waitpid after SIGCHLD failed: %s", strerror(err)); + break; + } + } + default: + if (WIFEXITED(status) || WIFSIGNALED(status)) + { + if (WIFEXITED(status)) + secdebug("child", "child with pid: %d exited: %d", pid, WEXITSTATUS(status)); + else if (WCOREDUMP(status)) + secdebug("child", "child with pid: %d terminated by signal: %d and dumped core", pid, WTERMSIG(status)); + else + secdebug("child", "child with pid: %d terminated by signal: %d", pid, WTERMSIG(status)); + + waitStatusForPid(pid, status); + } + else if (WSTOPSIG(status)) + { + secdebug("child", "child with pid: %d stopped by signal: %d", pid, WSTOPSIG(status)); + } + else + { + Syslog::error("child with pid: %d bogus waitpid status: %d", pid, status); + } + + tries = 1; + } + } +} + +void +ChildManager::waitStatusForPid(pid_t pid, int status) +{ + StLock _(mLock); + Child *child = findChild(pid); + if (child) + child->waitStatus(status); +} diff --git a/src/child.h b/src/child.h new file mode 100644 index 0000000..cd9b8a5 --- /dev/null +++ b/src/child.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// child - track a single child process and its belongings +// +#ifndef _CHILD_H_ +#define _CHILD_H_ 1 + +#include "securityserver.h" + +class Child; + +// +// ChildManager - Singleton Child Manager class +// +class ChildManager +{ +public: + // The singleton childManager. + static ChildManager childManager; + + ChildManager(); + ~ChildManager(); + + // Return true in the child. Return false in the parent and set outChildPort to 0 if the child died prematuely. In this case outWaitStatus contains the result of a waitpid() on the child. if outChildPort is non 0 it's a send right to contact the child on. + bool forkChild(mach_port_t &outChildPort, int &outWaitStatus); + + // SigChild signal handler + void handleSignal(int signal); + + // Be notified a child just contacted us on our main server port telling us it's child port. Set childPort to 0 iff we don't wish it to be deallocated by our caller. + void registerChild(pid_t pid, mach_port_t &childPort); + + // Assumes mLock is not locked (called by Child's eraseFromMap() function). + void eraseChild(pid_t pid); + + // Assumes mLock is already locked (called by Child's insertInMap() function). + void insertChild(pid_t pid, Child *child); + +private: + Child *findChild(pid_t pid); + + void handleSigChild(); + + // Be notifed of a term or signal waitpid() status. + void waitStatusForPid(pid_t pid, int status); + + + typedef std::map ChildMap; + ChildMap mChildMap; + Mutex mLock; +}; + + +#endif // _CHILD_H_ diff --git a/src/codesigdb.cpp b/src/codesigdb.cpp new file mode 100644 index 0000000..c148260 --- /dev/null +++ b/src/codesigdb.cpp @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// codesigdb - code-hash equivalence database +// +#include "codesigdb.h" +#include "process.h" +#include "server.h" +#include "agentquery.h" +#include + + +// +// A self-constructing database key class. +// Key format is +// where +// single ASCII character type code ('H' for hash links) +// decimal userid of owning user, or 'S' for system entries. Followed by null byte. +// variable length key value (binary). +// +class DbKey : public CssmAutoData { +public: + DbKey(char type, const CssmData &key, bool perUser = false, uid_t user = 0); +}; + +DbKey::DbKey(char type, const CssmData &key, bool perUser, uid_t user) + : CssmAutoData(Allocator::standard()) +{ + using namespace LowLevelMemoryUtilities; + char header[20]; + size_t headerLength; + if (perUser) + headerLength = 1 + sprintf(header, "%c%d", type, user); + else + headerLength = 1 + sprintf(header, "%cS", type); + malloc(headerLength + key.length()); + memcpy(this->data(), header, headerLength); + memcpy(get().at(headerLength), key.data(), key.length()); +} + + +// +// A subclass of Identity made of whole cloth (from a raw CodeSignature ACL information) +// +class AclIdentity : public CodeSignatures::Identity { +public: + AclIdentity(const CodeSigning::Signature *sig, const char *comment) + : mHash(*sig), mPath(comment ? comment : "") { } + AclIdentity(const CssmData &hash, const char *comment) + : mHash(hash), mPath(comment ? comment : "") { } + +protected: + std::string getPath() const { return mPath; } + const CssmData getHash(CodeSigning::OSXSigner &) const { return mHash; } + +private: + const CssmData mHash; + std::string mPath; +}; + + +// +// Construct a CodeSignatures objects +// +CodeSignatures::CodeSignatures(const char *path) +{ + try { + mDb.open(path, O_RDWR | O_CREAT, 0644); + } catch (const CommonError &err) { + try { + mDb.open(path, O_RDONLY, 0644); + Syslog::warning("database %s opened READONLY (R/W failed errno=%d)", path, err.unixError()); + secdebug("codesign", "database %s opened READONLY (R/W failed errno=%d)", path, err.unixError()); + } catch (...) { + Syslog::warning("cannot open %s; using no code equivalents", path); + secdebug("codesign", "unable to open %s; using no code equivalents", path); + } + } + if (mDb) + mDb.flush(); // in case we just created it + IFDUMPING("equiv", debugDump("open")); +} + +CodeSignatures::~CodeSignatures() +{ +} + + +// +// (Re)open the equivalence database. +// This is useful to switch to database in another volume. +// +void CodeSignatures::open(const char *path) +{ + mDb.open(path, O_RDWR | O_CREAT, 0644); + mDb.flush(); + IFDUMPING("equiv", debugDump("reopen")); +} + + +// +// Basic Identity objects +// +CodeSignatures::Identity::Identity() : mState(untried) +{ } + +CodeSignatures::Identity::~Identity() +{ } + +string CodeSignatures::Identity::canonicalName(const string &path) +{ + string::size_type slash = path.rfind('/'); + if (slash == string::npos) // bloody unlikely, but whatever... + return path; + return path.substr(slash+1); +} + + +// +// Find and store database objects (primitive layer) +// +bool CodeSignatures::find(Identity &id, uid_t user) +{ + if (id.mState != Identity::untried) + return id.mState == Identity::valid; + try { + DbKey userKey('H', id.getHash(mSigner), true, user); + CssmData linkValue; + if (mDb.get(userKey, linkValue)) { + id.mName = string(linkValue.interpretedAs(), linkValue.length()); + IFDUMPING("equiv", id.debugDump("found/user")); + id.mState = Identity::valid; + return true; + } + DbKey sysKey('H', id.getHash(mSigner)); + if (mDb.get(sysKey, linkValue)) { + id.mName = string(linkValue.interpretedAs(), linkValue.length()); + IFDUMPING("equiv", id.debugDump("found/system")); + id.mState = Identity::valid; + return true; + } + } catch (...) { + secdebug("codesign", "exception validating identity for %s - marking failed", id.path().c_str()); + id.mState = Identity::invalid; + } + return id.mState == Identity::valid; +} + +void CodeSignatures::makeLink(Identity &id, const string &ident, bool forUser, uid_t user) +{ + DbKey key('H', id.getHash(mSigner), forUser, user); + if (!mDb.put(key, StringData(ident))) + UnixError::throwMe(); +} + +void CodeSignatures::makeApplication(const std::string &name, const std::string &path) +{ + //@@@ create app record and fill (later) +} + + +// +// Administrative manipulation calls +// +void CodeSignatures::addLink(const CssmData &oldHash, const CssmData &newHash, + const char *inName, bool forSystem) +{ + string name = Identity::canonicalName(inName); + uid_t user = Server::process().uid(); + if (forSystem && user) // only root user can establish forSystem links + UnixError::throwMe(EACCES); + if (!forSystem) // in fact, for now we don't allow per-user calls at all + UnixError::throwMe(EACCES); + AclIdentity oldCode(oldHash, name.c_str()); + AclIdentity newCode(newHash, name.c_str()); + secdebug("codesign", "addlink for name %s", name.c_str()); + StLock _(mDatabaseLock); + if (oldCode) { + if (oldCode.trustedName() != name) { + secdebug("codesign", "addlink does not match existing name %s", + oldCode.trustedName().c_str()); + MacOSError::throwMe(CSSMERR_CSP_VERIFY_FAILED); + } + } else { + makeLink(oldCode, name, !forSystem, user); + } + if (!newCode) + makeLink(newCode, name, !forSystem, user); + mDb.flush(); +} + +void CodeSignatures::removeLink(const CssmData &hash, const char *name, bool forSystem) +{ + AclIdentity code(hash, name); + uid_t user = Server::process().uid(); + if (forSystem && user) // only root user can remove forSystem links + UnixError::throwMe(EACCES); + DbKey key('H', hash, !forSystem, user); + StLock _(mDatabaseLock); + mDb.erase(key); + mDb.flush(); +} + + +// +// Verify signature matches +// +bool CodeSignatures::verify(Process &process, + const CodeSigning::Signature *trustedSignature, const CssmData *comment) +{ + secdebug("codesign", "start verify"); + + // if we have no client code, we cannot possibly match this + if (!process.clientCode()) { + secdebug("codesign", "no code base: fail"); + return false; + } + + // first of all, if the signature directly matches the client's code, we're obviously fine + // we don't even need the database for that... + Identity &clientIdentity = process; + try { + if (clientIdentity.getHash(mSigner) == CssmData(*trustedSignature)) { + secdebug("codesign", "direct match: pass"); + return true; + } + } catch (...) { + secdebug("codesign", "exception getting client code hash: fail"); + return false; + } + + // ah well. Establish mediator objects for database signature links + AclIdentity aclIdentity(trustedSignature, comment ? comment->interpretedAs() : NULL); + + uid_t user = process.uid(); + { + StLock _(mDatabaseLock); + find(aclIdentity, user); + find(clientIdentity, user); + } + + // if both links exist, we can decide this right now + if (aclIdentity && clientIdentity) { + if (aclIdentity.trustedName() == clientIdentity.trustedName()) { + secdebug("codesign", "app references match: pass"); + return true; + } else { + secdebug("codesign", "client/acl links exist but are unequal: fail"); + return false; + } + } + + // check for name equality + secdebug("codesign", "matching client %s against acl %s", + clientIdentity.name().c_str(), aclIdentity.name().c_str()); + if (aclIdentity.name() != clientIdentity.name()) { + secdebug("codesign", "name/path mismatch: fail"); + return false; + } + + // The names match - we have a possible update. + + // Take the UI lock now to serialize "update rushes". + Server::active().longTermActivity(); + StLock uiLocker(mUILock); + + // re-read the database in case some other thread beat us to the update + { + StLock _(mDatabaseLock); + find(aclIdentity, user); + find(clientIdentity, user); + } + if (aclIdentity && clientIdentity) { + if (aclIdentity.trustedName() == clientIdentity.trustedName()) { + secdebug("codesign", "app references match: pass (on the rematch)"); + return true; + } else { + secdebug("codesign", "client/acl links exist but are unequal: fail (on the rematch)"); + return false; + } + } + + // ask the user + QueryCodeCheck query; + query.inferHints(process); + if (!query(aclIdentity.path().c_str())) + { + secdebug("codesign", "user declined equivalence: fail"); + return false; + } + + // take the database lock back for real + StLock _(mDatabaseLock); + + // user wants us to go ahead and establish trust (if possible) + if (aclIdentity) { + // acl is linked but new client: link the client to this application + makeLink(clientIdentity, aclIdentity.trustedName(), true, user); + mDb.flush(); + secdebug("codesign", "client %s linked to application %s: pass", + clientIdentity.path().c_str(), aclIdentity.trustedName().c_str()); + return true; + } + + if (clientIdentity) { // code link exists, acl link missing + // client is linked but ACL (hash) never seen: link the ACL to this app + makeLink(aclIdentity, clientIdentity.trustedName(), true, user); + mDb.flush(); + secdebug("codesign", "acl %s linked to client %s: pass", + aclIdentity.path().c_str(), clientIdentity.trustedName().c_str()); + return true; + } + + // the De Novo case: no links, must create everything + string ident = clientIdentity.name(); + makeApplication(ident, clientIdentity.path()); + makeLink(clientIdentity, ident, true, user); + makeLink(aclIdentity, ident, true, user); + mDb.flush(); + secdebug("codesign", "new linkages established: pass"); + return true; +} + + +// +// Debug dumping support +// +#if defined(DEBUGDUMP) + +void CodeSignatures::debugDump(const char *how) const +{ + using namespace Debug; + using namespace LowLevelMemoryUtilities; + if (!how) + how = "dump"; + CssmData key, value; + if (!mDb.first(key, value)) { + dump("CODE EQUIVALENTS DATABASE IS EMPTY (%s)\n", how); + } else { + dump("CODE EQUIVALENTS DATABASE DUMP (%s)\n", how); + do { + const char *header = key.interpretedAs(); + size_t headerLength = strlen(header) + 1; + dump("%s:", header); + dumpData(key.at(headerLength), key.length() - headerLength); + dump(" => "); + dumpData(value); + dump("\n"); + } while (mDb.next(key, value)); + dump("END DUMP\n"); + } +} + +void CodeSignatures::Identity::debugDump(const char *how) const +{ + using namespace Debug; + if (!how) + how = "dump"; + dump("IDENTITY (%s) path=%s", how, getPath().c_str()); + dump(" name=%s hash=", mName.empty() ? "(unset)" : mName.c_str()); + CodeSigning::OSXSigner signer; + dumpData(getHash(signer)); + dump("\n"); +} + +#endif //DEBUGDUMP \ No newline at end of file diff --git a/src/codesigdb.h b/src/codesigdb.h new file mode 100644 index 0000000..47a3173 --- /dev/null +++ b/src/codesigdb.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// codesigdb - code-hash equivalence database +// +#ifndef _H_CODESIGDB +#define _H_CODESIGDB + +#include +#include + + +class Process; +class CodeSignatures; + + +// +// A CodeSignaturse object represents a database of code-signature equivalencies +// as (previously) expressed by a user and/or the system. +// You'll usually only need one of these. +// +class CodeSignatures { +public: + // + // Identity is an abstract class modeling a code-identity in the database. + // It can represent either an existing or latent code-hash link. + // Subclass must provide path and hash source functions. + // + class Identity { + friend class CodeSignatures; + public: + Identity(); + virtual ~Identity(); + + operator bool () const { return mState == valid; } + std::string path() { return getPath(); } + std::string name() { return canonicalName(path()); } + std::string trustedName() const { return mName; } + + static std::string canonicalName(const std::string &path); + + IFDUMP(void debugDump(const char *how = NULL) const); + + virtual std::string getPath() const = 0; + virtual const CssmData getHash(CodeSigning::OSXSigner &signer) const = 0; + + private: + enum { untried, valid, invalid } mState; + std::string mName; // link db value (canonical name linked to) + }; + +public: + CodeSignatures(const char *path); + ~CodeSignatures(); + + void open(const char *path); + +public: + bool find(Identity &id, uid_t user); + + void makeLink(Identity &id, const std::string &ident, bool forUser = false, uid_t user = 0); + void makeApplication(const std::string &name, const std::string &path); + + void addLink(const CssmData &oldHash, const CssmData &newHash, + const char *name, bool forSystem); + void removeLink(const CssmData &hash, const char *name, bool forSystem); + + IFDUMP(void debugDump(const char *how = NULL) const); + +public: + bool verify(Process &process, + const CodeSigning::Signature *trustedSignature, const CssmData *comment); + +private: + UnixPlusPlus::UnixDb mDb; + CodeSigning::OSXSigner mSigner; + + // lock hierarchy: mUILock first, then mDatabaseLock, no back-off + Mutex mDatabaseLock; // controls mDb access + Mutex mUILock; // serializes user interaction +}; + + + +#endif //_H_CODESIGDB diff --git a/src/connection.cpp b/src/connection.cpp new file mode 100644 index 0000000..d99dcc9 --- /dev/null +++ b/src/connection.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// connection - manage connections to clients. +// +// Note that Connection objects correspond to client process threads, and are +// thus inherently single-threaded. It is physically impossible for multiple +// requests to come in for the same Connection, unless the client side is +// illegally messing with the IPC protocol (for which we check below). +// It is still necessary to take the object lock for a Connection because there +// are times when we want to manipulate a busy Connection from another securityd +// thread (say, in response to a DPN). +// +#include "connection.h" +#include "key.h" +#include "server.h" +#include "session.h" +#include +#include +#include +#include +#include +#include + + +// +// Construct a Connection object. +// +Connection::Connection(Process &proc, Port rPort) + : mClientPort(rPort), state(idle), agentWait(NULL), + aclUpdateTrigger(NULL) +{ + parent(proc); + + // bump the send-rights count on the reply port so we keep the right after replying + mClientPort.modRefs(MACH_PORT_RIGHT_SEND, +1); + + secdebug("SS", "New connection %p for process %d clientport=%d", + this, process().pid(), int(rPort)); +} + + +// +// When a Connection's destructor executes, the connection must already have been +// terminated. All we have to do here is clean up a bit. +// +Connection::~Connection() +{ + secdebug("SS", "Connection %p destroyed", this); + assert(!agentWait); +} + + +// +// Terminate a Connection normally. +// This is assumed to be properly sequenced, so no thread races are possible. +// +void Connection::terminate() +{ + // cleanly discard port rights + assert(state == idle); + mClientPort.modRefs(MACH_PORT_RIGHT_SEND, -1); // discard surplus send right + assert(mClientPort.getRefs(MACH_PORT_RIGHT_SEND) == 1); // one left for final reply + secdebug("SS", "Connection %p terminated", this); +} + + +// +// Abort a Connection. +// This may be called from thread A while thread B is working a request for the Connection, +// so we must be careful. +// +void Connection::abort(bool keepReplyPort) +{ + StLock _(*this); + if (!keepReplyPort) + mClientPort.destroy(); // dead as a doornail already + switch (state) { + case idle: + secdebug("SS", "Connection %p aborted", this); + break; + case busy: + state = dying; // shoot me soon, please + if (agentWait) + agentWait->destroy(); + secdebug("SS", "Connection %p abort deferred (busy)", this); + break; + default: + assert(false); // impossible (we hope) + break; + } +} + + +// +// Service request framing. +// These are here so "hanging" connection service threads don't fall +// into the Big Bad Void as Connections and processes drop out from +// under them. +// +void Connection::beginWork() +{ + switch (state) { + case idle: + state = busy; + break; + case busy: + secdebug("SS", "Attempt to re-enter connection %p(port %d)", this, mClientPort.port()); + CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ some state-error code instead? + default: + assert(false); + } +} + +void Connection::checkWork() +{ + StLock _(*this); + switch (state) { + case busy: + return; + case dying: + agentWait = NULL; // obviously we're not waiting on this + throw this; + default: + assert(false); + } +} + +void Connection::endWork() +{ + switch (state) { + case busy: + // process the n-step aclUpdateTrigger + if (aclUpdateTrigger) { + if (--aclUpdateTriggerCount == 0) { + aclUpdateTrigger = NULL; + secdebug("kcacl", "acl update trigger expires"); + } else + secdebug("kcacl", "acl update trigger armed for %d calls", + aclUpdateTriggerCount); + } + // end involvement + state = idle; + return; + case dying: + secdebug("SS", "Connection %p abort resuming", this); + return; + default: + assert(false); + return; // placebo + } +} diff --git a/src/connection.h b/src/connection.h new file mode 100644 index 0000000..03c7dbd --- /dev/null +++ b/src/connection.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// connection - manage connections to clients +// +#ifndef _H_CONNECTION +#define _H_CONNECTION + +#include "securityserver.h" +#include +#include +#include "process.h" +#include "session.h" +#include "key.h" +#include + +using MachPlusPlus::Port; +using MachPlusPlus::TaskPort; + +class Session; + + +// +// A Connection object represents an established connection between a client +// and the SecurityServer. Note that in principle, a client process can have +// multiple Connections (each represented by an IPC channel), though there will +// usually be only one. +// +class Connection : public PerConnection { + typedef Key::Handle KeyHandle; +public: + Connection(Process &proc, Port rPort); + virtual ~Connection(); + void terminate(); // normal termination + void abort(bool keepReplyPort = false); // abnormal termination + + Port clientPort() const { return mClientPort; } + + // work framing - called as work threads pick up connection work + void beginWork(); // I've got it + void checkWork(); // everything still okay? + void endWork(); // Done with this + + // notify that a SecurityAgent call may hang the active worker thread for a while + void useAgent(SecurityAgent::Client *client) + { StLock _(*this); agentWait = client; } + + // special UI convenience - set a don't-ask-again trigger for Keychain-style ACLs + void setAclUpdateTrigger(const SecurityServerAcl &object) + { aclUpdateTrigger = &object; aclUpdateTriggerCount = aclUpdateTriggerLimit + 1; } + bool aclWasSetForUpdateTrigger(const SecurityServerAcl &object) const + { return aclUpdateTriggerCount > 0 && aclUpdateTrigger == &object; } + + Process &process() const { return parent(); } + Session &session() const { return process().session(); } + +private: + // peer state: established during connection startup; fixed thereafter + Port mClientPort; + + // transient state (altered as we go) + enum State { + idle, // no thread services us + busy, // a thread is busy servicing us + dying // busy and scheduled to die as soon as possible + } state; + SecurityAgent::Client *agentWait; // SA client session we may be waiting on + + // see KeychainPromptAclSubject in acl_keychain.cpp for more information on this + const SecurityServerAcl *aclUpdateTrigger; // update trigger set for this (NULL if none) + uint8 aclUpdateTriggerCount; // number of back-to-back requests honored + static const uint8 aclUpdateTriggerLimit = 3; // 3 calls (getAcl+getOwner+changeAcl) +}; + + +#endif //_H_CONNECTION diff --git a/src/database.cpp b/src/database.cpp new file mode 100644 index 0000000..a7b7242 --- /dev/null +++ b/src/database.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// database - database session management +// +#include "database.h" +#include "agentquery.h" +#include "key.h" +#include "server.h" +#include "session.h" +#include +#include // for default owner ACLs +#include +#include + + +// +// DbCommon basics +// +DbCommon::DbCommon(Session &session) +{ + referent(session); +} + +Session &DbCommon::session() const +{ + return referent(); +} + + +// +// Database basics +// +Database::Database(Process &proc) + : SecurityServerAcl(dbAcl, Allocator::standard()) +{ + referent(proc); +} + + +Process& Database::process() const +{ + return referent(); +} + + +// +// Default behaviors +// +void DbCommon::sleepProcessing() +{ + // nothing +} + + +void Database::releaseKey(Key &key) +{ + removeReference(key); +} + + +// +// Implementation of a "system keychain unlock key store" +// +SystemKeychainKey::SystemKeychainKey(const char *path) + : mPath(path) +{ + // explicitly set up a key header for a raw 3DES key + CssmKey::Header &hdr = mKey.header(); + hdr.blobType(CSSM_KEYBLOB_RAW); + hdr.blobFormat(CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING); + hdr.keyClass(CSSM_KEYCLASS_SESSION_KEY); + hdr.algorithm(CSSM_ALGID_3DES_3KEY_EDE); + hdr.KeyAttr = 0; + hdr.KeyUsage = CSSM_KEYUSE_ANY; + mKey = CssmData::wrap(mBlob.masterKey); +} + +SystemKeychainKey::~SystemKeychainKey() +{ +} + +bool SystemKeychainKey::matches(const DbBlob::Signature &signature) +{ + return update() && signature == mBlob.signature; +} + +bool SystemKeychainKey::update() +{ + // if we checked recently, just assume it's okay + if (mUpdateThreshold > Time::now()) + return mValid; + + // check the file + struct stat st; + if (::stat(mPath.c_str(), &st)) { + // something wrong with the file; can't use it + mUpdateThreshold = Time::now() + Time::Interval(checkDelay); + return mValid = false; + } + if (mValid && Time::Absolute(st.st_mtimespec) == mCachedDate) + return true; + mUpdateThreshold = Time::now() + Time::Interval(checkDelay); + + try { + secdebug("syskc", "reading system unlock record from %s", mPath.c_str()); + AutoFileDesc fd(mPath, O_RDONLY); + if (fd.read(mBlob) != sizeof(mBlob)) + return false; + if (mBlob.isValid()) { + mCachedDate = st.st_mtimespec; + return mValid = true; + } else + return mValid = false; + } catch (...) { + secdebug("syskc", "system unlock record not available"); + return false; + } +} diff --git a/src/database.h b/src/database.h new file mode 100644 index 0000000..dc3c3a9 --- /dev/null +++ b/src/database.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// database - abstract database management +// +// This file defines database objects that represent different +// way to implement "database with cryptographic operations on its contents". +// The objects here are abstract and need to be implemented to be useful. +// +#ifndef _H_DATABASE +#define _H_DATABASE + +#include "securityserver.h" +#include "structure.h" +#include "acls.h" +#include "dbcrypto.h" +#include "notifications.h" +#include +#include +#include +#include +#include +#include +#include +#include + + +class Key; +class Connection; +class Process; +class Session; +using MachPlusPlus::MachServer; + + +// +// A Database::DbCommon is the "common core" of all Database objects that +// represent the same client database (on disk, presumably). +// NOTE: DbCommon obeys exterior locking protocol: the caller (always Database) +// must lock it before operating on its non-const members. In practice, +// most Database methods lock down their DbCommon first thing. +// +class DbCommon : public PerSession { +public: + DbCommon(Session &ssn); + + Session &session() const; + + virtual void sleepProcessing(); +}; + + +// +// A Database object represents an Apple CSP/DL open database (DL/DB) object. +// It maintains its protected semantic state (including keys) and provides controlled +// access. +// +class Database : public PerProcess, public SecurityServerAcl { + static const NotificationEvent lockedEvent = kNotificationEventLocked; + static const NotificationEvent unlockedEvent = kNotificationEventUnlocked; + static const NotificationEvent passphraseChangedEvent = kNotificationEventPassphraseChanged; + +protected: + Database(Process &proc); + +public: + Process& process() const; + + virtual void releaseKey(Key &key); + virtual CSSM_KEY_SIZE queryKeySize(Key &key) = 0; + + // service calls + virtual void generateSignature(const Context &context, Key &key, + CSSM_ALGORITHMS signOnlyAlgorithm, const CssmData &data, CssmData &signature) = 0; + virtual void verifySignature(const Context &context, Key &key, + CSSM_ALGORITHMS verifyOnlyAlgorithm, const CssmData &data, const CssmData &signature) = 0; + virtual void generateMac(const Context &context, Key &key, + const CssmData &data, CssmData &mac) = 0; + virtual void verifyMac(const Context &context, Key &key, + const CssmData &data, const CssmData &mac) = 0; + + virtual void encrypt(const Context &context, Key &key, const CssmData &clear, CssmData &cipher) = 0; + virtual void decrypt(const Context &context, Key &key, const CssmData &cipher, CssmData &clear) = 0; + + virtual void generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, RefPointer &newKey) = 0; + virtual void generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 pubUsage, uint32 pubAttrs, uint32 privUsage, uint32 privAttrs, + RefPointer &publicKey, RefPointer &privateKey) = 0; + virtual RefPointer deriveKey(const Context &context, Key *key, + const AccessCredentials *cred, const AclEntryPrototype *owner, + CssmData *param, uint32 usage, uint32 attrs) = 0; + + virtual void wrapKey(const Context &context, Key *key, + Key &keyToBeWrapped, const AccessCredentials *cred, + const CssmData &descriptiveData, CssmKey &wrappedKey) = 0; + virtual RefPointer unwrapKey(const Context &context, Key *key, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, const CssmKey wrappedKey, + Key *publicKey, CssmData *descriptiveData) = 0; + + virtual uint32 getOutputSize(const Context &context, Key &key, + uint32 inputSize, bool encrypt = true) = 0; + + virtual void authenticate(const AccessCredentials *cred) = 0; + +public: + static const int maxUnlockTryCount = 3; + +public: + DbCommon& common() const { return parent(); } + virtual const char *dbName() const = 0; + +protected: + AccessCredentials *mCred; // local access credentials (always valid) +}; + + +// +// This class implements a "system keychaiin unlock record" store +// +class SystemKeychainKey { +public: + SystemKeychainKey(const char *path); + ~SystemKeychainKey(); + + bool matches(const DbBlob::Signature &signature); + CssmKey &key() { return mKey; } + +private: + std::string mPath; // path to file + CssmKey mKey; // proper CssmKey with data in mBlob + + bool mValid; // mBlob was validly read from mPath + UnlockBlob mBlob; // contents of mPath as last read + + Time::Absolute mCachedDate; // modify date of file when last read + Time::Absolute mUpdateThreshold; // cutoff threshold for checking again + + static const int checkDelay = 1; // seconds minimum delay between update checks + + bool update(); +}; + +#endif //_H_DATABASE diff --git a/src/dbcrypto.cpp b/src/dbcrypto.cpp new file mode 100644 index 0000000..f885440 --- /dev/null +++ b/src/dbcrypto.cpp @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// dbcrypto - cryptographic core for database and key blob cryptography +// +#include "dbcrypto.h" +#include +#include "server.h" // just for Server::csp() +#include +#include +#include +#include +#include +#include + + +using namespace CssmClient; + + +DatabaseCryptoCore::DatabaseCryptoCore() : mHaveMaster(false), mIsValid(false) +{ +} + + +DatabaseCryptoCore::~DatabaseCryptoCore() +{ + // key objects take care of themselves +} + + +// +// Forget the secrets +// +void DatabaseCryptoCore::invalidate() +{ + mMasterKey.release(); + mHaveMaster = false; + + mEncryptionKey.release(); + mSigningKey.release(); + mIsValid = false; +} + + +// +// Generate new secrets for this crypto core. +// +void DatabaseCryptoCore::generateNewSecrets() +{ + // create a random DES3 key + GenerateKey desGenerator(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE, 24 * 8); + mEncryptionKey = desGenerator(KeySpec(CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP, + CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE)); + + // create a random 20 byte HMAC/SHA1 signing "key" + GenerateKey signGenerator(Server::csp(), CSSM_ALGID_SHA1HMAC, + sizeof(DbBlob::PrivateBlob::SigningKey) * 8); + mSigningKey = signGenerator(KeySpec(CSSM_KEYUSE_SIGN | CSSM_KEYUSE_VERIFY, + CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE)); + + // secrets established + mIsValid = true; +} + + +CssmClient::Key DatabaseCryptoCore::masterKey() +{ + assert(mHaveMaster); + return mMasterKey; +} + + +// +// Establish the master secret as derived from a passphrase passed in. +// If a DbBlob is passed, take the salt from it and remember it. +// If a NULL DbBlob is passed, generate a new (random) salt. +// Note that the passphrase is NOT remembered; only the master key. +// +void DatabaseCryptoCore::setup(const DbBlob *blob, const CssmData &passphrase) +{ + if (blob) + memcpy(mSalt, blob->salt, sizeof(mSalt)); + else + Server::active().random(mSalt); + mMasterKey = deriveDbMasterKey(passphrase); + mHaveMaster = true; +} + + +// +// Establish the master secret directly from a master key passed in. +// We will copy the KeyData (caller still owns its copy). +// Blob/salt handling as above. +// +void DatabaseCryptoCore::setup(const DbBlob *blob, CssmClient::Key master) +{ + // pre-screen the key + CssmKey::Header header = master.header(); + if (header.keyClass() != CSSM_KEYCLASS_SESSION_KEY) + CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS); + if (header.algorithm() != CSSM_ALGID_3DES_3KEY_EDE) + CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); + + // accept it + if (blob) + memcpy(mSalt, blob->salt, sizeof(mSalt)); + else + Server::active().random(mSalt); + mMasterKey = master; + mHaveMaster = true; +} + + +// +// Given a putative passphrase, determine whether that passphrase +// properly generates the database's master secret. +// Return a boolean accordingly. Do not change our state. +// The database must have a master secret (to compare with). +// Note that any errors thrown by the cryptography here will actually +// throw out of validatePassphrase, since they "should not happen" and +// thus indicate a problem *beyond* (just) a bad passphrase. +// +bool DatabaseCryptoCore::validatePassphrase(const CssmData &passphrase) +{ + assert(hasMaster()); + CssmClient::Key master = deriveDbMasterKey(passphrase); + + // to compare master with mMaster, see if they encrypt alike + StringData probe + ("Now is the time for all good processes to come to the aid of their kernel."); + CssmData noRemainder((void *)1, 0); // no cipher overflow + Encrypt cryptor(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); + cryptor.mode(CSSM_ALGMODE_CBCPadIV8); + cryptor.padding(CSSM_PADDING_PKCS1); + uint8 iv[8]; // leave uninitialized; pseudo-random is cool + cryptor.initVector(CssmData::wrap(iv)); + + cryptor.key(master); + CssmAutoData cipher1(Server::csp().allocator()); + cryptor.encrypt(probe, cipher1.get(), noRemainder); + + cryptor.key(mMasterKey); + CssmAutoData cipher2(Server::csp().allocator()); + cryptor.encrypt(probe, cipher2.get(), noRemainder); + + return cipher1 == cipher2; +} + + +// +// Encode a database blob from the core. +// +DbBlob *DatabaseCryptoCore::encodeCore(const DbBlob &blobTemplate, + const CssmData &publicAcl, const CssmData &privateAcl) const +{ + assert(isValid()); // must have secrets to work from + + // make a new IV + uint8 iv[8]; + Server::active().random(iv); + + // build the encrypted section blob + CssmData &encryptionBits = *mEncryptionKey; + CssmData &signingBits = *mSigningKey; + CssmData incrypt[3]; + incrypt[0] = encryptionBits; + incrypt[1] = signingBits; + incrypt[2] = privateAcl; + CssmData cryptoBlob, remData; + Encrypt cryptor(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); + cryptor.mode(CSSM_ALGMODE_CBCPadIV8); + cryptor.padding(CSSM_PADDING_PKCS1); + cryptor.key(mMasterKey); + CssmData ivd(iv, sizeof(iv)); cryptor.initVector(ivd); + cryptor.encrypt(incrypt, 3, &cryptoBlob, 1, remData); + + // allocate the final DbBlob, uh, blob + size_t length = sizeof(DbBlob) + publicAcl.length() + cryptoBlob.length(); + DbBlob *blob = Allocator::standard().malloc(length); + + // assemble the DbBlob + memset(blob, 0x7d, sizeof(DbBlob)); // deterministically fill any alignment gaps + blob->initialize(); + blob->randomSignature = blobTemplate.randomSignature; + blob->sequence = blobTemplate.sequence; + blob->params = blobTemplate.params; + memcpy(blob->salt, mSalt, sizeof(blob->salt)); + memcpy(blob->iv, iv, sizeof(iv)); + memcpy(blob->publicAclBlob(), publicAcl, publicAcl.length()); + blob->startCryptoBlob = sizeof(DbBlob) + publicAcl.length(); + memcpy(blob->cryptoBlob(), cryptoBlob, cryptoBlob.length()); + blob->totalLength = blob->startCryptoBlob + cryptoBlob.length(); + + // sign the blob + CssmData signChunk[] = { + CssmData(blob->data(), offsetof(DbBlob, blobSignature)), + CssmData(blob->publicAclBlob(), publicAcl.length() + cryptoBlob.length()) + }; + CssmData signature(blob->blobSignature, sizeof(blob->blobSignature)); + GenerateMac signer(Server::csp(), CSSM_ALGID_SHA1HMAC_LEGACY); //@@@!!! CRUD + signer.key(mSigningKey); + signer.sign(signChunk, 2, signature); + assert(signature.length() == sizeof(blob->blobSignature)); + + // all done. Clean up + Server::csp()->allocator().free(cryptoBlob); + return blob; +} + + +// +// Decode a database blob into the core. +// Throws exceptions if decoding fails. +// Memory returned in privateAclBlob is allocated and becomes owned by caller. +// +void DatabaseCryptoCore::decodeCore(DbBlob *blob, void **privateAclBlob) +{ + assert(mHaveMaster); // must have master key installed + + // try to decrypt the cryptoblob section + Decrypt decryptor(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); + decryptor.mode(CSSM_ALGMODE_CBCPadIV8); + decryptor.padding(CSSM_PADDING_PKCS1); + decryptor.key(mMasterKey); + CssmData ivd(blob->iv, sizeof(blob->iv)); decryptor.initVector(ivd); + CssmData cryptoBlob(blob->cryptoBlob(), blob->cryptoBlobLength()); + CssmData decryptedBlob, remData; + decryptor.decrypt(cryptoBlob, decryptedBlob, remData); + DbBlob::PrivateBlob *privateBlob = decryptedBlob.interpretedAs(); + + // tentatively establish keys + mEncryptionKey = makeRawKey(privateBlob->encryptionKey, + sizeof(privateBlob->encryptionKey), CSSM_ALGID_3DES_3KEY_EDE, + CSSM_KEYUSE_WRAP | CSSM_KEYUSE_UNWRAP); + mSigningKey = makeRawKey(privateBlob->signingKey, + sizeof(privateBlob->signingKey), CSSM_ALGID_SHA1HMAC, + CSSM_KEYUSE_SIGN | CSSM_KEYUSE_VERIFY); + + // verify signature on the whole blob + CssmData signChunk[] = { + CssmData(blob->data(), offsetof(DbBlob, blobSignature)), + CssmData(blob->publicAclBlob(), blob->publicAclBlobLength() + blob->cryptoBlobLength()) + }; + CSSM_ALGORITHMS verifyAlgorithm = CSSM_ALGID_SHA1HMAC; +#if defined(COMPAT_OSX_10_0) + if (blob->version() == blob->version_MacOS_10_0) + verifyAlgorithm = CSSM_ALGID_SHA1HMAC_LEGACY; // BSafe bug compatibility +#endif + VerifyMac verifier(Server::csp(), verifyAlgorithm); + verifier.key(mSigningKey); + verifier.verify(signChunk, 2, CssmData(blob->blobSignature, sizeof(blob->blobSignature))); + + // all checks out; start extracting fields + this->mEncryptionKey = mEncryptionKey; + this->mSigningKey = mSigningKey; + if (privateAclBlob) { + // extract private ACL blob as a separately allocated area + uint32 blobLength = decryptedBlob.length() - sizeof(DbBlob::PrivateBlob); + *privateAclBlob = Allocator::standard().malloc(blobLength); + memcpy(*privateAclBlob, privateBlob->privateAclBlob(), blobLength); + } + + // secrets have been established + mIsValid = true; + Allocator::standard().free(privateBlob); +} + + +// +// Encode a key blob +// +KeyBlob *DatabaseCryptoCore::encodeKeyCore(const CssmKey &inKey, + const CssmData &publicAcl, const CssmData &privateAcl) const +{ + assert(isValid()); // need our database secrets + + // create new IV + uint8 iv[8]; + Server::active().random(iv); + + // extract and hold some header bits the CSP does not want to see + CssmKey key = inKey; + uint32 heldAttributes = key.attributes() & managedAttributes; + key.clearAttribute(managedAttributes); + key.setAttribute(forcedAttributes); + + // use a CMS wrap to encrypt the key + WrapKey wrap(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); + wrap.key(mEncryptionKey); + wrap.mode(CSSM_ALGMODE_CBCPadIV8); + wrap.padding(CSSM_PADDING_PKCS1); + CssmData ivd(iv, sizeof(iv)); wrap.initVector(ivd); + wrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT, + uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM)); + CssmKey wrappedKey; + wrap(key, wrappedKey, &privateAcl); + + // stick the held attribute bits back in + key.clearAttribute(forcedAttributes); + key.setAttribute(heldAttributes); + + // allocate the final KeyBlob, uh, blob + size_t length = sizeof(KeyBlob) + publicAcl.length() + wrappedKey.length(); + KeyBlob *blob = Allocator::standard().malloc(length); + + // assemble the KeyBlob + memset(blob, 0, sizeof(KeyBlob)); // fill alignment gaps + blob->initialize(); + memcpy(blob->iv, iv, sizeof(iv)); + blob->header = key.header(); + h2ni(blob->header); // endian-correct the header + blob->wrappedHeader.blobType = wrappedKey.blobType(); + blob->wrappedHeader.blobFormat = wrappedKey.blobFormat(); + blob->wrappedHeader.wrapAlgorithm = wrappedKey.wrapAlgorithm(); + blob->wrappedHeader.wrapMode = wrappedKey.wrapMode(); + memcpy(blob->publicAclBlob(), publicAcl, publicAcl.length()); + blob->startCryptoBlob = sizeof(KeyBlob) + publicAcl.length(); + memcpy(blob->cryptoBlob(), wrappedKey.data(), wrappedKey.length()); + blob->totalLength = blob->startCryptoBlob + wrappedKey.length(); + + // sign the blob + CssmData signChunk[] = { + CssmData(blob->data(), offsetof(KeyBlob, blobSignature)), + CssmData(blob->publicAclBlob(), blob->publicAclBlobLength() + blob->cryptoBlobLength()) + }; + CssmData signature(blob->blobSignature, sizeof(blob->blobSignature)); + GenerateMac signer(Server::csp(), CSSM_ALGID_SHA1HMAC_LEGACY); //@@@!!! CRUD + signer.key(mSigningKey); + signer.sign(signChunk, 2, signature); + assert(signature.length() == sizeof(blob->blobSignature)); + + // all done. Clean up + Server::csp()->allocator().free(wrappedKey); + return blob; +} + + +// +// Decode a key blob +// +void DatabaseCryptoCore::decodeKeyCore(KeyBlob *blob, + CssmKey &key, void * &pubAcl, void * &privAcl) const +{ + assert(isValid()); // need our database secrets + + // Assemble the encrypted blob as a CSSM "wrapped key" + CssmKey wrappedKey; + wrappedKey.KeyHeader = blob->header; + h2ni(wrappedKey.KeyHeader); + wrappedKey.blobType(blob->wrappedHeader.blobType); + wrappedKey.blobFormat(blob->wrappedHeader.blobFormat); + wrappedKey.wrapAlgorithm(blob->wrappedHeader.wrapAlgorithm); + wrappedKey.wrapMode(blob->wrappedHeader.wrapMode); + wrappedKey.KeyData = CssmData(blob->cryptoBlob(), blob->cryptoBlobLength()); + + // verify signature (check against corruption) + CssmData signChunk[] = { + CssmData::wrap(blob, offsetof(KeyBlob, blobSignature)), + CssmData(blob->publicAclBlob(), blob->publicAclBlobLength() + blob->cryptoBlobLength()) + }; + CSSM_ALGORITHMS verifyAlgorithm = CSSM_ALGID_SHA1HMAC; +#if defined(COMPAT_OSX_10_0) + if (blob->version() == blob->version_MacOS_10_0) + verifyAlgorithm = CSSM_ALGID_SHA1HMAC_LEGACY; // BSafe bug compatibility +#endif + VerifyMac verifier(Server::csp(), verifyAlgorithm); + verifier.key(mSigningKey); + CssmData signature(blob->blobSignature, sizeof(blob->blobSignature)); + verifier.verify(signChunk, 2, signature); + + // extract and hold some header bits the CSP does not want to see + uint32 heldAttributes = n2h(blob->header.attributes()) & managedAttributes; + + // decrypt the key using an unwrapping operation + UnwrapKey unwrap(Server::csp(), CSSM_ALGID_3DES_3KEY_EDE); + unwrap.key(mEncryptionKey); + unwrap.mode(CSSM_ALGMODE_CBCPadIV8); + unwrap.padding(CSSM_PADDING_PKCS1); + CssmData ivd(blob->iv, sizeof(blob->iv)); unwrap.initVector(ivd); + unwrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT, + uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_APPLE_CUSTOM)); + CssmData privAclData; + wrappedKey.clearAttribute(managedAttributes); //@@@ shouldn't be needed(?) + unwrap(wrappedKey, + KeySpec(n2h(blob->header.usage()), + (n2h(blob->header.attributes()) & ~managedAttributes) | forcedAttributes), + key, &privAclData); + + // compare retrieved key headers with blob headers (sanity check) + // @@@ this should probably be checked over carefully + CssmKey::Header &real = key.header(); + CssmKey::Header &incoming = blob->header; + n2hi(incoming); + + if (real.HeaderVersion != incoming.HeaderVersion || + real.cspGuid() != incoming.cspGuid()) + CssmError::throwMe(CSSMERR_CSP_INVALID_KEY); + if (real.algorithm() != incoming.algorithm()) + CssmError::throwMe(CSSMERR_CSP_INVALID_ALGORITHM); + + // re-insert held bits + key.header().KeyAttr |= heldAttributes; + + // got a valid key: return the pieces + pubAcl = blob->publicAclBlob(); // points into blob (shared) + privAcl = privAclData; // was allocated by CSP decrypt + // key was set by unwrap operation +} + + +// +// Derive the blob-specific database blob encryption key from the passphrase and the salt. +// +CssmClient::Key DatabaseCryptoCore::deriveDbMasterKey(const CssmData &passphrase) const +{ + // derive an encryption key and IV from passphrase and salt + CssmClient::DeriveKey makeKey(Server::csp(), + CSSM_ALGID_PKCS5_PBKDF2, CSSM_ALGID_3DES_3KEY_EDE, 24 * 8); + makeKey.iterationCount(1000); + makeKey.salt(CssmData::wrap(mSalt)); + CSSM_PKCS5_PBKDF2_PARAMS params; + params.Passphrase = passphrase; + params.PseudoRandomFunction = CSSM_PKCS5_PBKDF2_PRF_HMAC_SHA1; + CssmData paramData = CssmData::wrap(params); + return makeKey(¶mData, KeySpec(CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, + CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE)); +} + + +// +// Turn raw keybits into a symmetric key in the CSP +// +CssmClient::Key DatabaseCryptoCore::makeRawKey(void *data, size_t length, + CSSM_ALGORITHMS algid, CSSM_KEYUSE usage) +{ + // build a fake key + CssmKey key; + key.header().BlobType = CSSM_KEYBLOB_RAW; + key.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING; + key.header().AlgorithmId = algid; + key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY; + key.header().KeyUsage = usage; + key.header().KeyAttr = 0; + key.KeyData = CssmData(data, length); + + // unwrap it into the CSP (but keep it raw) + UnwrapKey unwrap(Server::csp(), CSSM_ALGID_NONE); + CssmKey unwrappedKey; + CssmData descriptiveData; + unwrap(key, + KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_RETURN_DATA | CSSM_KEYATTR_EXTRACTABLE), + unwrappedKey, &descriptiveData, NULL); + return CssmClient::Key(Server::csp(), unwrappedKey); +} diff --git a/src/dbcrypto.h b/src/dbcrypto.h new file mode 100644 index 0000000..00a6a19 --- /dev/null +++ b/src/dbcrypto.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2000-2001,2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// dbcrypto - cryptographic core for database and key blob cryptography +// +#ifndef _H_DBCRYPTO +#define _H_DBCRYPTO + +#include "securityserver.h" +#include +#include + + +// +// A DatabaseCryptoCore object encapsulates the secret state of a database. +// It provides for encoding and decoding of database blobs and key blobs, +// and holds all state related to the database secrets. +// +class DatabaseCryptoCore { +public: + DatabaseCryptoCore(); + virtual ~DatabaseCryptoCore(); + + bool isValid() const { return mIsValid; } + bool hasMaster() const { return mHaveMaster; } + void invalidate(); + + void generateNewSecrets(); + CssmClient::Key masterKey(); + + void setup(const DbBlob *blob, const CssmData &passphrase); + void setup(const DbBlob *blob, CssmClient::Key master); + + void decodeCore(DbBlob *blob, void **privateAclBlob = NULL); + DbBlob *encodeCore(const DbBlob &blobTemplate, + const CssmData &publicAcl, const CssmData &privateAcl) const; + + KeyBlob *encodeKeyCore(const CssmKey &key, + const CssmData &publicAcl, const CssmData &privateAcl) const; + void decodeKeyCore(KeyBlob *blob, + CssmKey &key, void * &pubAcl, void * &privAcl) const; + + static const uint32 managedAttributes = KeyBlob::managedAttributes; + static const uint32 forcedAttributes = KeyBlob::forcedAttributes; + +public: + bool validatePassphrase(const CssmData &passphrase); + +private: + bool mHaveMaster; // master key has been entered (setup) + bool mIsValid; // master secrets are valid (decode or generateNew) + + CssmClient::Key mMasterKey; // database master key + uint8 mSalt[20]; // salt for master key derivation from passphrase (only) + + CssmClient::Key mEncryptionKey; // master encryption key + CssmClient::Key mSigningKey; // master signing key + + CssmClient::Key deriveDbMasterKey(const CssmData &passphrase) const; + CssmClient::Key makeRawKey(void *data, size_t length, + CSSM_ALGORITHMS algid, CSSM_KEYUSE usage); +}; + + +#endif //_H_DBCRYPTO diff --git a/src/entropy.cpp b/src/entropy.cpp new file mode 100644 index 0000000..7a2dccc --- /dev/null +++ b/src/entropy.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// EntropyManager - manage entropy on the system. +// +// Here is our mission: +// (1) On startup, read the entropy file and seed it into the RNG for initial use +// (2) Periodically, collect entropy from the system and seed it into the RNG +// (3) Once in a while, take entropy from the RNG and write it to the entropy file +// for use across reboots. +// +// This class will fail to operate if the process has (and retains) root privileges. +// We re-open the entropy file on each use so that we don't work with a "phantom" +// file that some fool administrator removed yesterday. +// +#include "entropy.h" +#include +#include +#include +#include +#include +#include + +/* when true, action() called every 15 seconds */ +#define ENTROPY_QUICK_UPDATE 0 +#if ENTROPY_QUICK_UPDATE +#define COLLECT_INTERVAL 15 +#else +#define COLLECT_INTERVAL collectInterval +#endif //ENTROPY_QUICK_UPDATE + +using namespace UnixPlusPlus; + + +// +// During construction, we perform initial entropy file recovery. +// +EntropyManager::EntropyManager(MachPlusPlus::MachServer &srv, const char *entropyFile) + : DevRandomGenerator(true), server(srv), + mEntropyFilePath(entropyFile), mNextUpdate(Time::now()) +{ + // Read the entropy file and seed the RNG. It is not an error if we can't find one. + try { + AutoFileDesc oldEntropyFile(entropyFile, O_RDONLY); + char buffer[entropyFileSize]; + if (size_t size = oldEntropyFile.read(buffer)) + addEntropy(buffer, size); + } catch (...) { } + + // go through a collect/update/reschedule cycle immediately + action(); +} + + +// +// Timer action +// +void EntropyManager::action() +{ + collectEntropy(); + updateEntropyFile(); + + server.setTimer(this, Time::Interval(COLLECT_INTERVAL)); // drifting reschedule (desired) +} + + +// +// Collect system timings and seed into the RNG. +// Note that the sysctl will block until the buffer is full or the timeout expires. +// We currently use a 1ms timeout, which almost always fills the buffer and +// does not provide enough of a delay to worry about it. If we ever get worried, +// we could call longTermActivity on the server object to get another thread going. +// +void EntropyManager::collectEntropy() +{ + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDGETENTROPY; + mib[3] = 1; // milliseconds maximum delay + mach_timespec_t timings[timingsToCollect]; + size_t size = sizeof(timings); + int ret = sysctl(mib, 4, timings, &size, NULL, 0); + if (ret == -1) { + Syslog::alert("entropy collection failed (errno=%d)", errno); + return; + } + char buffer[timingsToCollect]; + for (unsigned n = 0; n < size; n++) + buffer[n] = timings[n].tv_nsec; // truncating to LSB + secdebug("entropy", "Entropy size %d: %02x %02x %02x %02x %02x %02x %02x %02x...", + (int)size, + (unsigned char)buffer[0], (unsigned char)buffer[1], (unsigned char)buffer[2], + (unsigned char)buffer[3], (unsigned char)buffer[4], (unsigned char)buffer[5], + (unsigned char)buffer[6], (unsigned char)buffer[7]); + addEntropy(buffer, size); +} + + +// +// (Re)write the entropy file with random data pulled from the RNG +// +void EntropyManager::updateEntropyFile() +{ + if (Time::now() >= mNextUpdate) { + try { + mNextUpdate = Time::now() + Time::Interval(updateInterval); + secdebug("entropy", "updating %s", mEntropyFilePath.c_str()); + char buffer[entropyFileSize]; + random(buffer, entropyFileSize); + AutoFileDesc entropyFile(mEntropyFilePath.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0600); + if (entropyFile.write(buffer) != entropyFileSize) + Syslog::warning("short write on entropy file %s", mEntropyFilePath.c_str()); + } catch (...) { + Syslog::warning("error writing entropy file %s", mEntropyFilePath.c_str()); + } + } +} + diff --git a/src/entropy.h b/src/entropy.h new file mode 100644 index 0000000..2dbdc07 --- /dev/null +++ b/src/entropy.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// yarrowseed - periodical to collect and seed entropy into /dev/random +// +#ifndef _H_ENTROPY +#define _H_ENTROPY + +#include +#include +#include + +using namespace Security; +using MachPlusPlus::MachServer; + + +// +// A (one-off) timer object that manages system entropy +// +class EntropyManager : public MachServer::Timer, private DevRandomGenerator { + // all the parameters you ever (should) want to change :-) + static const int collectInterval = 600; // collect every 10 minutes + static const int updateInterval = 3600; // update file every hour + static const int timingsToCollect = 40; // how many timings? + +public: + EntropyManager(MachPlusPlus::MachServer &srv, const char *entropyFile); + + void action(); + + MachPlusPlus::MachServer &server; // to which we do setTimer() + +private: + string mEntropyFilePath; // absolute path to entropy file + Time::Absolute mNextUpdate; // next time for entropy file update + + void collectEntropy(); // collect system timings and seed RNG + void updateEntropyFile(); // update entropy file from RNG if it's time + + static const size_t entropyFileSize = 20; // bytes (effectively one SHA-1 worth) +}; + +#endif //_H_ENTROPY diff --git a/src/flippers.cpp b/src/flippers.cpp new file mode 100644 index 0000000..694fd9e --- /dev/null +++ b/src/flippers.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// process - track a single client process and its belongings +// +#include "flippers.h" +#include + +using namespace LowLevelMemoryUtilities; + + +namespace Flippers { + + +// +// Automatically generated flippers +// +#include "flip_gen.cpp" + + +// +// The raw byte reversal flipper +// +void flip(void *addr, size_t size) +{ + assert(size > 1 && (size % 2 == 0)); + Byte *word = reinterpret_cast(addr); + for (size_t n = 0; n < size/2; n++) { + Byte b = word[n]; + word[n] = word[size-1-n]; + word[size-1-n] = b; + } +} + + +// +// Basic flippers +// +void flip(uint32 &obj) { flip(&obj, sizeof(obj)); } +void flip(uint16 &obj) { flip(&obj, sizeof(obj)); } +void flip(sint32 &obj) { flip(&obj, sizeof(obj)); } +void flip(sint16 &obj) { flip(&obj, sizeof(obj)); } + + +// +// Flip a context attribute. This is heavily polymorphic. +// +void flip(CSSM_CONTEXT_ATTRIBUTE &obj) +{ + flip(obj.AttributeType); + flip(obj.AttributeLength); + switch (obj.AttributeType & CSSM_ATTRIBUTE_TYPE_MASK) { + case CSSM_ATTRIBUTE_DATA_UINT32: + flip(obj.Attribute.Uint32); + break; + // all other alternatives are handled by CSSM_CONTEXT_ATTRIBUTE's walker + default: + break; + } +} + + +} // end namespace Flippers diff --git a/src/flippers.h b/src/flippers.h new file mode 100644 index 0000000..140c6cf --- /dev/null +++ b/src/flippers.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// process - track a single client process and its belongings +// +#ifndef _H_FLIPPERS +#define _H_FLIPPERS + +#include +#include + +// various types we make flippers for +#include +#include +#include +#include +#include +#include +#include + + +namespace Flippers { + + +// +// The default flipper does nothing +// +template +inline void flip(T &obj) +{ } + + +// +// It's a bad idea to try to flip a const, so flag that +// +template +inline void flip(const T &) +{ tryingToFlipAConstWontWork(); } + + +// +// Basic flippers +// +void flip(uint32 &obj); +void flip(uint16 &obj); +void flip(sint32 &obj); +void flip(sint16 &obj); + +template +inline void flip(Base * &obj) { flip(&obj, sizeof(obj)); } + + +// +// The raw byte reversal flipper +// +void flip(void *addr, size_t size); + + +// +// Include automatically generated flipper declarations +// +#include "flip_gen.h" + + +} // end namespace flippers + + +#endif //_H_FLIPPERS diff --git a/src/generate.cf b/src/generate.cf new file mode 100644 index 0000000..8557c3e --- /dev/null +++ b/src/generate.cf @@ -0,0 +1,42 @@ +# +# Byte flipper generator configuration. +# +# Syntax of each non-comment line: +# cssmType[/podwrapperType] field1 ... fieldn +# cssmType[/podwrapperType] * +# Generates flippers for each cssmType (with forwarders for podwrapperType if present), +# flipping the fields given. If '*' is used, take field definitions from cssmtype.h. +# + +# +# CSSM standard structures +# +CSSM_DATA/CssmData Length +CSSM_VERSION * +CSSM_SUBSERVICE_UID/CssmSubserviceUid * +CSSM_NET_ADDRESS * +CSSM_LIST_ELEMENT/ListElement WordID ElementType +CSSM_DL_DB_HANDLE * +CSSM_CONTEXT_ATTRIBUTE/Context::Attr CUSTOM +CSSM_CONTEXT/Context * +CSSM_LIST/CssmList/TypedList ListType Tail +CSSM_SAMPLE/CssmSample * +CSSM_SAMPLEGROUP/SampleGroup NumberOfSamples +CSSM_ACCESS_CREDENTIALS/AccessCredentials Callback CallerCtx +CSSM_AUTHORIZATIONGROUP/AuthorizationGroup NumberOfAuthTags +CSSM_ACL_VALIDITY_PERIOD * +CSSM_ACL_ENTRY_PROTOTYPE/AclEntryPrototype Delegate +CSSM_ACL_OWNER_PROTOTYPE/AclOwnerPrototype Delegate +CSSM_ACL_ENTRY_INPUT/AclEntryInput Callback CallerContext +CSSM_ACL_ENTRY_INFO/AclEntryInfo EntryHandle +CSSM_RANGE * +CSSM_KEY_SIZE/CssmKeySize * +CSSM_KEYHEADER/CssmKey::Header * +CSSM_KEY/CssmKey KeyHeader + + +# +# Authorization structures +# +AuthorizationItem valueLength flags +AuthorizationItemSet count diff --git a/src/generate.mk b/src/generate.mk new file mode 100644 index 0000000..112c477 --- /dev/null +++ b/src/generate.mk @@ -0,0 +1,39 @@ +# +# Makefile to build MIG-generated sources and headers +# +XSRCROOT:=$(shell cd $(SRCROOT) >/dev/null; pwd) +TARGET:=$(shell cd $(BUILT_PRODUCTS_DIR) >/dev/null; pwd) +SRC:=$(TARGET)/derived_src +HDR:=$(TARGET)/include + +build: $(SRC)/.mig.ucsp $(SRC)/.mig.secagent + +debug: build + +profile: build + +install: build + +installhdrs: build + +installsrc: + +clean: + rm -f $(SRC)/.mig.ucsp $(SRC)/.mig.secagent \ + $(SRC)/ucsp*.cpp $(SRC)/secagent*.cpp $(HDR)/ucsp.h $(HDR)/secagent.h + +$(SRC)/.mig.ucsp: SecurityServer/ucsp.defs SecurityServer/ucspNotify.defs SecurityServer/ucsp_types.h + mkdir -p $(SRC) + mkdir -p $(HDR) + cd /tmp; mig -server $(SRC)/ucspServer.cpp -user $(SRC)/ucspUser.cpp \ + -header $(HDR)/ucsp.h $(XSRCROOT)/SecurityServer/ucsp.defs + cd /tmp; mig -server $(SRC)/ucspNotifyReceiver.cpp -user $(SRC)/ucspNotifySender.cpp \ + -header $(HDR)/ucspNotify.h $(XSRCROOT)/SecurityServer/ucspNotify.defs + touch $(SRC)/.mig.ucsp + +$(SRC)/.mig.secagent: SecurityServer/secagent.defs SecurityServer/secagent_types.h + mkdir -p $(SRC) + mkdir -p $(HDR) + cd /tmp; mig -server $(SRC)/secagentServer.cpp -user $(SRC)/secagentUser.cpp \ + -header $(HDR)/secagent.h $(XSRCROOT)/SecurityServer/secagent.defs + touch $(SRC)/.mig.secagent diff --git a/src/generate.pl b/src/generate.pl new file mode 100755 index 0000000..3426267 --- /dev/null +++ b/src/generate.pl @@ -0,0 +1,71 @@ +#!/usr/bin/perl +# +# +# +use strict; + +my $disclaimer = "Automatically generated - do not edit on penalty of futility!"; + + +# arguments +my ($configfile, $out_h, $out_cpp, $types) = @ARGV; + + +# open configuration file +open(CFG, "$configfile") || die "$configfile: $!"; + +# open and load cssmtypes file +open(TYPES, "$types") || die "$types: $!"; +$/=undef; +my $types_h = ; +close(TYPES); $/="\n"; + +# open output files +open(H, ">$out_h") || die "$out_h: $!"; +open(CPP, ">$out_cpp") || die "$out_cpp: $!"; + +# cautionary headings to each file +print H <) { + chomp; + next if /^[ ]*#/; + next if /^[ ]*$/; + + my @args = split; + $_ = shift @args; + my ($cssmName, @aliases) = split /\//; + + print H "void flip($cssmName &obj);\n"; + for my $alias (@aliases) { + print H "inline void flip($alias &obj) { flip(static_cast<$cssmName &>(obj)); }\n"; + } + + next if ($args[0] eq 'CUSTOM'); + + if ($args[0] eq '*') { + # extract definition from types file + my ($list) = $types_h =~ /{\s+([^}]+)\s+}\s*$cssmName,/; + die "cannot find struct definition for $cssmName in $types" unless $list; + @args = $list =~ /([A-Za-z0-9_]+);/gm; + } + + print CPP "void flip($cssmName &obj)\n{\n"; + for my $field (@args) { + print CPP "\tflip(obj.$field);\n"; + } + print CPP "}\n\n"; +} diff --git a/src/kcdatabase.cpp b/src/kcdatabase.cpp new file mode 100644 index 0000000..e66c8eb --- /dev/null +++ b/src/kcdatabase.cpp @@ -0,0 +1,893 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// kcdatabase - software database container implementation. +// +// General implementation notes: +// This leverages LocalDatabase/LocalKey for cryptography, and adds the +// storage coder/decoder logic that implements "keychain" databases in their +// intricately choreographed dance between securityd and the AppleCSPDL. +// As always, Database objects are lifetime-bound to their Process referent; +// they can also be destroyed explicitly with a client release call. +// DbCommons are reference-held by their Databases, with one extra special +// reference (from the Session) introduced when the database unlocks, and +// removed when it locks again. That way, an unused DbCommon dies when it +// is locked or when the Session dies, whichever happens earlier. +// There is (as yet) no global-scope Database object for Keychain databases. +// +#include "kcdatabase.h" +#include "agentquery.h" +#include "kckey.h" +#include "server.h" +#include "session.h" +#include "notifications.h" +#include +#include // for default owner ACLs +#include +#include +#include +#include +#include +#include +#include + + +// +// Create a Database object from initial parameters (create operation) +// +KeychainDatabase::KeychainDatabase(const DLDbIdentifier &id, const DBParameters ¶ms, Process &proc, + const AccessCredentials *cred, const AclEntryPrototype *owner) + : LocalDatabase(proc), mValidData(false), version(0), mBlob(NULL) +{ + // save a copy of the credentials for later access control + mCred = DataWalkers::copy(cred, Allocator::standard()); + + // create a new random signature to complete the DLDbIdentifier + DbBlob::Signature newSig; + Server::active().random(newSig.bytes); + DbIdentifier ident(id, newSig); + + // create common block and initialize + RefPointer newCommon = new KeychainDbCommon(proc.session(), ident); + StLock _(*newCommon); + parent(*newCommon); + // new common is now visible (in ident-map) but we hold its lock + + // establish the new master secret + establishNewSecrets(cred, SecurityAgent::newDatabase); + + // set initial database parameters + common().mParams = params; + + // the common is "unlocked" now + common().makeNewSecrets(); + + // establish initial ACL + if (owner) + cssmSetInitial(*owner); + else + cssmSetInitial(new AnyAclSubject()); + mValidData = true; + + // for now, create the blob immediately + encode(); + + proc.addReference(*this); + secdebug("SSdb", "database %s(%p) created, common at %p", + common().dbName(), this, &common()); +} + + +// +// Create a Database object from a database blob (decoding) +// +KeychainDatabase::KeychainDatabase(const DLDbIdentifier &id, const DbBlob *blob, Process &proc, + const AccessCredentials *cred) + : LocalDatabase(proc), mValidData(false), version(0) +{ + // perform basic validation on the incoming blob + assert(blob); + blob->validate(CSSMERR_APPLEDL_INVALID_DATABASE_BLOB); + switch (blob->version()) { +#if defined(COMPAT_OSX_10_0) + case blob->version_MacOS_10_0: + break; +#endif + case blob->version_MacOS_10_1: + break; + default: + CssmError::throwMe(CSSMERR_APPLEDL_INCOMPATIBLE_DATABASE_BLOB); + } + + // save a copy of the credentials for later access control + mCred = DataWalkers::copy(cred, Allocator::standard()); + mBlob = blob->copy(); + + // check to see if we already know about this database + DbIdentifier ident(id, blob->randomSignature); + Session &session = process().session(); + StLock _(session); + if (KeychainDbCommon *dbcom = + session.findFirst(&KeychainDbCommon::identifier, ident)) { + parent(*dbcom); + //@@@ arbitrate sequence number here, perhaps update common().mParams + secdebug("SSdb", + "open database %s(%p) version %lx at known common %p", + common().dbName(), this, blob->version(), &common()); + } else { + // DbCommon not present; make a new one + parent(*new KeychainDbCommon(proc.session(), ident)); + common().mParams = blob->params; + secdebug("SSdb", "open database %s(%p) version %lx with new common %p", + common().dbName(), this, blob->version(), &common()); + } + proc.addReference(*this); +} + + +// +// Destroy a Database +// +KeychainDatabase::~KeychainDatabase() +{ + secdebug("SSdb", "deleting database %s(%p) common %p", + common().dbName(), this, &common()); + Allocator::standard().free(mCred); + Allocator::standard().free(mBlob); +} + + +// +// Basic Database virtual implementations +// +KeychainDbCommon &KeychainDatabase::common() const +{ + return parent(); +} + +const char *KeychainDatabase::dbName() const +{ + return common().dbName(); +} + + +static inline KeychainKey &myKey(Key *key) +{ + return *safe_cast(key); +} + + +// +// (Re-)Authenticate the database. This changes the stored credentials. +// +void KeychainDatabase::authenticate(const AccessCredentials *cred) +{ + StLock _(common()); + AccessCredentials *newCred = DataWalkers::copy(cred, Allocator::standard()); + Allocator::standard().free(mCred); + mCred = newCred; +} + + +// +// Make a new KeychainKey +// +RefPointer KeychainDatabase::makeKey(const CssmKey &newKey, + uint32 moreAttributes, const AclEntryPrototype *owner) +{ + return new KeychainKey(*this, newKey, moreAttributes, owner); +} + + +// +// Return the database blob, recalculating it as needed. +// +DbBlob *KeychainDatabase::blob() +{ + StLock _(common()); + if (!validBlob()) { + makeUnlocked(); // unlock to get master secret + encode(); // (re)encode blob if needed + } + activity(); // reset timeout + assert(validBlob()); // better have a valid blob now... + return mBlob; +} + + +// +// Encode the current database as a blob. +// Note that this returns memory we own and keep. +// Caller must hold common lock. +// +void KeychainDatabase::encode() +{ + DbBlob *blob = common().encode(*this); + Allocator::standard().free(mBlob); + mBlob = blob; + version = common().version; + secdebug("SSdb", "encoded database %p common %p(%s) version %ld params=(%ld,%d)", + this, &common(), dbName(), version, + common().mParams.idleTimeout, common().mParams.lockOnSleep); +} + + +// +// Change the passphrase on a database +// +void KeychainDatabase::changePassphrase(const AccessCredentials *cred) +{ + // get and hold the common lock (don't let other threads break in here) + StLock _(common()); + + // establish OLD secret - i.e. unlock the database + //@@@ do we want to leave the final lock state alone? + makeUnlocked(cred); + + // establish NEW secret + establishNewSecrets(cred, SecurityAgent::changePassphrase); + common().version++; // blob state changed + secdebug("SSdb", "Database %s(%p) master secret changed", common().dbName(), this); + encode(); // force rebuild of local blob + + // send out a notification + notify(kNotificationEventPassphraseChanged); + + // I guess this counts as an activity + activity(); +} + + +// +// Extract the database master key as a proper Key object. +// +RefPointer KeychainDatabase::extractMasterKey(Database &db, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs) +{ + // get and hold common lock + StLock _(common()); + + // unlock to establish master secret + makeUnlocked(cred); + + // extract the raw cryptographic key + CssmClient::WrapKey wrap(Server::csp(), CSSM_ALGID_NONE); + CssmKey key; + wrap(common().masterKey(), key); + + // make the key object and return it + return new KeychainKey(db, key, attrs & Key::managedAttributes, owner); +} + + +// +// Construct a binary blob of moderate size that is suitable for constructing +// an index identifying this database. +// We construct this from the database's marker blob, which is created with +// the database is made, and stays stable thereafter. +// Note: Ownership of the index blob passes to the caller. +// @@@ This means that physical copies share this index. +// +void KeychainDatabase::getDbIndex(CssmData &indexData) +{ + if (!mBlob) + encode(); // force blob creation + assert(mBlob); + CssmData signature = CssmData::wrap(mBlob->randomSignature); + indexData = CssmAutoData(Allocator::standard(), signature).release(); +} + + +// +// Unlock this database (if needed) by obtaining the master secret in some +// suitable way and then proceeding to unlock with it. +// Does absolutely nothing if the database is already unlocked. +// The makeUnlocked forms are identical except the assume the caller already +// holds the common lock. +// +void KeychainDatabase::unlockDb() +{ + StLock _(common()); + makeUnlocked(); +} + +void KeychainDatabase::makeUnlocked() +{ + return makeUnlocked(mCred); +} + +void KeychainDatabase::makeUnlocked(const AccessCredentials *cred) +{ + if (isLocked()) { + secdebug("SSdb", "%p(%p) unlocking for makeUnlocked()", this, &common()); + assert(mBlob || (mValidData && common().hasMaster())); + establishOldSecrets(cred); + common().setUnlocked(); // mark unlocked + activity(); // set timeout timer + } else if (!mValidData) { // need to decode to get our ACLs, master secret available + secdebug("SSdb", "%p(%p) is unlocked; decoding for makeUnlocked()", this, &common()); + if (!decode()) + CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); + } + assert(!isLocked()); + assert(mValidData); +} + + +// +// The following unlock given an explicit passphrase, rather than using +// (special cred sample based) default procedures. +// +void KeychainDatabase::unlockDb(const CssmData &passphrase) +{ + StLock _(common()); + makeUnlocked(passphrase); +} + +void KeychainDatabase::makeUnlocked(const CssmData &passphrase) +{ + if (isLocked()) { + if (decode(passphrase)) + return; + else + CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); + } else if (!mValidData) { // need to decode to get our ACLs, passphrase available + if (!decode()) + CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); + } + assert(!isLocked()); + assert(mValidData); +} + + +// +// Nonthrowing passphrase-based unlock. This returns false if unlock failed. +// Note that this requires an explicitly given passphrase. +// Caller must hold common lock. +// +bool KeychainDatabase::decode(const CssmData &passphrase) +{ + assert(mBlob); + common().setup(mBlob, passphrase); + return decode(); +} + + +// +// Given the established master secret, decode the working keys and other +// functional secrets for this database. Return false (do NOT throw) if +// the decode fails. Call this in low(er) level code once you established +// the master key. +// +bool KeychainDatabase::decode() +{ + assert(mBlob); + assert(common().hasMaster()); + void *privateAclBlob; + if (common().unlockDb(mBlob, &privateAclBlob)) { + if (!mValidData) { + importBlob(mBlob->publicAclBlob(), privateAclBlob); + mValidData = true; + } + Allocator::standard().free(privateAclBlob); + return true; + } + secdebug("SSdb", "%p decode failed", this); + return false; +} + + +// +// Given an AccessCredentials for this database, wring out the existing primary +// database secret by whatever means necessary. +// On entry, caller must hold the database common lock. It will be held throughout. +// On exit, the crypto core has its master secret. If things go wrong, +// we will throw a suitable exception. Note that encountering any malformed +// credential sample will throw, but this is not guaranteed -- don't assume +// that NOT throwing means creds is entirely well-formed (it may just be good +// enough to work THIS time). +// +// How this works: +// Walk through the creds. Fish out those credentials (in order) that +// are for unlock processing (they have no ACL subject correspondents), +// and (try to) obey each in turn, until one produces a valid secret +// or you run out. If no special samples are found at all, interpret that as +// "use the system global default," which happens to be hard-coded right here. +// +void KeychainDatabase::establishOldSecrets(const AccessCredentials *creds) +{ + list samples; + if (creds && creds->samples().collect(CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, samples)) { + for (list::iterator it = samples.begin(); it != samples.end(); it++) { + TypedList &sample = *it; + sample.checkProper(); + switch (sample.type()) { + // interactively prompt the user - no additional data + case CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT: + { + secdebug("SSdb", "%p attempting interactive unlock", this); + QueryUnlock query(*this); + query.inferHints(Server::process()); + if (query() == SecurityAgent::noReason) + return; + } + break; + // try to use an explicitly given passphrase - Data:passphrase + case CSSM_SAMPLE_TYPE_PASSWORD: + if (sample.length() != 2) + CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE); + secdebug("SSdb", "%p attempting passphrase unlock", this); + if (decode(sample[1])) + return; + break; + // try to open with a given master key - Data:CSP or KeyHandle, Data:CssmKey + case CSSM_WORDID_SYMMETRIC_KEY: + assert(mBlob); + secdebug("SSdb", "%p attempting explicit key unlock", this); + common().setup(mBlob, keyFromCreds(sample)); + if (decode()) + return; + break; + // explicitly defeat the default action but don't try anything in particular + case CSSM_WORDID_CANCELED: + secdebug("SSdb", "%p defeat default action", this); + break; + default: + // Unknown sub-sample for unlocking. + // If we wanted to be fascist, we could now do + // CssmError::throwMe(CSSM_ERRCODE_SAMPLE_VALUE_NOT_SUPPORTED); + // But instead we try to be tolerant and continue on. + // This DOES however count as an explicit attempt at specifying unlock, + // so we will no longer try the default case below... + secdebug("SSdb", "%p unknown sub-sample unlock (%ld) ignored", this, sample.type()); + break; + } + } + } else { + // default action + assert(mBlob); + + // attempt system-keychain unlock + SystemKeychainKey systemKeychain(kSystemUnlockFile); + if (systemKeychain.matches(mBlob->randomSignature)) { + secdebug("SSdb", "%p attempting system unlock", this); + common().setup(mBlob, CssmClient::Key(Server::csp(), systemKeychain.key(), true)); + if (decode()) + return; + } + + // attempt interactive unlock + QueryUnlock query(*this); + query.inferHints(Server::process()); + if (query() == SecurityAgent::noReason) + return; + } + + // out of options - no secret obtained + CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); +} + + +// +// Same thing, but obtain a new secret somehow and set it into the common. +// +void KeychainDatabase::establishNewSecrets(const AccessCredentials *creds, SecurityAgent::Reason reason) +{ + list samples; + if (creds && creds->samples().collect(CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, samples)) { + for (list::iterator it = samples.begin(); it != samples.end(); it++) { + TypedList &sample = *it; + sample.checkProper(); + switch (sample.type()) { + // interactively prompt the user + case CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT: + { + secdebug("SSdb", "%p specified interactive passphrase", this); + QueryNewPassphrase query(*this, reason); + query.inferHints(Server::process()); + CssmAutoData passphrase(Allocator::standard(Allocator::sensitive)); + if (query(passphrase) == SecurityAgent::noReason) { + common().setup(NULL, passphrase); + return; + } + } + break; + // try to use an explicitly given passphrase + case CSSM_SAMPLE_TYPE_PASSWORD: + secdebug("SSdb", "%p specified explicit passphrase", this); + if (sample.length() != 2) + CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE); + common().setup(NULL, sample[1]); + return; + // try to open with a given master key + case CSSM_WORDID_SYMMETRIC_KEY: + secdebug("SSdb", "%p specified explicit master key", this); + common().setup(NULL, keyFromCreds(sample)); + return; + // explicitly defeat the default action but don't try anything in particular + case CSSM_WORDID_CANCELED: + secdebug("SSdb", "%p defeat default action", this); + break; + default: + // Unknown sub-sample for acquiring new secret. + // If we wanted to be fascist, we could now do + // CssmError::throwMe(CSSM_ERRCODE_SAMPLE_VALUE_NOT_SUPPORTED); + // But instead we try to be tolerant and continue on. + // This DOES however count as an explicit attempt at specifying unlock, + // so we will no longer try the default case below... + secdebug("SSdb", "%p unknown sub-sample acquisition (%ld) ignored", + this, sample.type()); + break; + } + } + } else { + // default action -- interactive (only) + QueryNewPassphrase query(*this, reason); + query.inferHints(Server::process()); + CssmAutoData passphrase(Allocator::standard(Allocator::sensitive)); + if (query(passphrase) == SecurityAgent::noReason) { + common().setup(NULL, passphrase); + return; + } + } + + // out of options - no secret obtained + CssmError::throwMe(CSSM_ERRCODE_OPERATION_AUTH_DENIED); +} + + +// +// Given a (truncated) Database credentials TypedList specifying a master key, +// locate the key and return a reference to it. +// +CssmClient::Key KeychainDatabase::keyFromCreds(const TypedList &sample) +{ + // decode TypedList structure (sample type; Data:CSPHandle; Data:CSSM_KEY) + assert(sample.type() == CSSM_WORDID_SYMMETRIC_KEY); + if (sample.length() != 3 + || sample[1].type() != CSSM_LIST_ELEMENT_DATUM + || sample[2].type() != CSSM_LIST_ELEMENT_DATUM) + CssmError::throwMe(CSSM_ERRCODE_INVALID_SAMPLE_VALUE); + CSSM_CSP_HANDLE &handle = *sample[1].data().interpretedAs(CSSM_ERRCODE_INVALID_SAMPLE_VALUE); + CssmKey &key = *sample[2].data().interpretedAs(CSSM_ERRCODE_INVALID_SAMPLE_VALUE); + + if (key.header().cspGuid() == gGuidAppleCSPDL) { + // handleOrKey is a SecurityServer KeyHandle; ignore key argument + return myKey(Server::key(handle)); + } else { + // not a KeyHandle reference; use key as a raw key + if (key.header().blobType() != CSSM_KEYBLOB_RAW) + CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE); + if (key.header().keyClass() != CSSM_KEYCLASS_SESSION_KEY) + CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_CLASS); + return CssmClient::Key(Server::csp(), key, true); + } +} + + +// +// Verify a putative database passphrase. +// If the database is already unlocked, just check the passphrase. +// Otherwise, unlock with that passphrase and report success. +// Caller must hold the common lock. +// +bool KeychainDatabase::validatePassphrase(const CssmData &passphrase) const +{ + if (common().hasMaster()) { + // verify against known secret + return common().validatePassphrase(passphrase); + } else { + // no master secret - perform "blind" unlock to avoid actual unlock + try { + DatabaseCryptoCore test; + test.setup(mBlob, passphrase); + test.decodeCore(mBlob, NULL); + return true; + } catch (...) { + return false; + } + } +} + + +// +// Lock this database +// +void KeychainDatabase::lockDb() +{ + common().lockDb(); +} + + +// +// Given a Key for this database, encode it into a blob and return it. +// +KeyBlob *KeychainDatabase::encodeKey(const CssmKey &key, const CssmData &pubAcl, const CssmData &privAcl) +{ + unlockDb(); + + // tell the cryptocore to form the key blob + return common().encodeKeyCore(key, pubAcl, privAcl); +} + + +// +// Given a "blobbed" key for this database, decode it into its real +// key object and (re)populate its ACL. +// +void KeychainDatabase::decodeKey(KeyBlob *blob, CssmKey &key, void * &pubAcl, void * &privAcl) +{ + unlockDb(); // we need our keys + + common().decodeKeyCore(blob, key, pubAcl, privAcl); + // memory protocol: pubAcl points into blob; privAcl was allocated + + activity(); +} + + +// +// Modify database parameters +// +void KeychainDatabase::setParameters(const DBParameters ¶ms) +{ + StLock _(common()); + makeUnlocked(); + common().mParams = params; + common().version++; // invalidate old blobs + activity(); + secdebug("SSdb", "%p common %p(%s) set params=(%ld,%d)", + this, &common(), dbName(), params.idleTimeout, params.lockOnSleep); +} + + +// +// Retrieve database parameters +// +void KeychainDatabase::getParameters(DBParameters ¶ms) +{ + StLock _(common()); + makeUnlocked(); + params = common().mParams; + //activity(); // getting parameters does not reset the idle timer +} + + +// +// Intercept ACL change requests and reset blob validity +// +void KeychainDatabase::instantiateAcl() +{ + StLock _(common()); + makeUnlocked(); +} + +void KeychainDatabase::changedAcl() +{ + StLock _(common()); + version = 0; +} + +const Database *KeychainDatabase::relatedDatabase() const +{ return this; } + + +// +// Debugging support +// +#if defined(DEBUGDUMP) + +void KeychainDbCommon::dumpNode() +{ + PerSession::dumpNode(); + uint32 sig; memcpy(&sig, &mIdentifier.signature(), sizeof(sig)); + Debug::dump(" %s[%8.8lx]", mIdentifier.dbName(), sig); + if (isLocked()) { + Debug::dump(" locked"); + } else { + time_t whenTime = time_t(when()); + Debug::dump(" unlocked(%24.24s/%.2g)", ctime(&whenTime), + (when() - Time::now()).seconds()); + } + Debug::dump(" params=(%ld,%d)", mParams.idleTimeout, mParams.lockOnSleep); +} + +void KeychainDatabase::dumpNode() +{ + PerProcess::dumpNode(); + Debug::dump(" %s vers=%ld", + mValidData ? " data" : " nodata", version); + if (mBlob) { + uint32 sig; memcpy(&sig, &mBlob->randomSignature, sizeof(sig)); + Debug::dump(" blob=%p[%8.8lx]", mBlob, sig); + } else { + Debug::dump(" noblob"); + } +} + +#endif //DEBUGDUMP + + +// +// DbCommon basic features +// +KeychainDbCommon::KeychainDbCommon(Session &ssn, const DbIdentifier &id) + : DbCommon(ssn), mIdentifier(id), sequence(0), version(1), + mIsLocked(true), mValidParams(false) +{ } + +KeychainDbCommon::~KeychainDbCommon() +{ + secdebug("SSdb", "DbCommon %p destroyed", this); + + // explicitly unschedule ourselves + Server::active().clearTimer(this); +} + + +void KeychainDbCommon::makeNewSecrets() +{ + // we already have a master key (right?) + assert(hasMaster()); + + // tell crypto core to generate the use keys + DatabaseCryptoCore::generateNewSecrets(); + + // we're now officially "unlocked"; set the timer + setUnlocked(); + activity(); // set lock timer +} + + +// +// All unlocking activity ultimately funnels through this method. +// This unlocks a DbCommon using the secrets setup in its crypto core +// component, and performs all the housekeeping needed to represent +// the state change. +// Returns true if unlock was successful, false if it failed due to +// invalid/insufficient secrets. Throws on other errors. +// +bool KeychainDbCommon::unlockDb(DbBlob *blob, void **privateAclBlob) +{ + try { + // Tell the cryptocore to (try to) decode itself. This will fail + // in an astonishing variety of ways if the passphrase is wrong. + assert(hasMaster()); + decodeCore(blob, privateAclBlob); + secdebug("SSdb", "%p unlock successful", this); + } catch (...) { + secdebug("SSdb", "%p unlock failed", this); + return false; + } + + // get the database parameters only if we haven't got them yet + if (!mValidParams) { + mParams = blob->params; + n2hi(mParams.idleTimeout); + mValidParams = true; // sticky + } + + setUnlocked(); // mark unlocked + activity(); // set lock timer + + // broadcast unlock notification + notify(kNotificationEventUnlocked); + return true; +} + +void KeychainDbCommon::setUnlocked() +{ + session().addReference(*this); // active/held + mIsLocked = false; // mark unlocked +} + + +void KeychainDbCommon::lockDb(bool forSleep) +{ + StLock locker(*this); + if (!isLocked()) { + if (forSleep && !mParams.lockOnSleep) + return; // it doesn't want to + + DatabaseCryptoCore::invalidate(); + notify(kNotificationEventLocked); + Server::active().clearTimer(this); + + mIsLocked = true; // mark locked + locker.unlock(); // release DbCommon lock now + session().removeReference(*this); // remove active/hold + } +} + + +DbBlob *KeychainDbCommon::encode(KeychainDatabase &db) +{ + assert(!isLocked()); // must have been unlocked by caller + + // export database ACL to blob form + CssmData pubAcl, privAcl; + db.exportBlob(pubAcl, privAcl); + + // tell the cryptocore to form the blob + DbBlob form; + form.randomSignature = identifier(); + form.sequence = sequence; + form.params = mParams; + h2ni(form.params.idleTimeout); + + assert(hasMaster()); + DbBlob *blob = encodeCore(form, pubAcl, privAcl); + + // clean up and go + db.allocator.free(pubAcl); + db.allocator.free(privAcl); + return blob; +} + + +// +// Send a keychain-related notification event about this keychain +// +void KeychainDbCommon::notify(NotificationEvent event) +{ + // form the data (encoded DLDbIdentifier) + NameValueDictionary nvd; + NameValueDictionary::MakeNameValueDictionaryFromDLDbIdentifier(identifier(), nvd); + CssmData data; + nvd.Export(data); + + // inject notification into Security event system + Listener::notify(kNotificationDomainDatabase, event, data); + + // clean up + free (data.data()); +} + + +// +// Perform deferred lock processing for a database. +// +void KeychainDbCommon::action() +{ + secdebug("SSdb", "common %s(%p) locked by timer", dbName(), this); + lockDb(); +} + +void KeychainDbCommon::activity() +{ + if (!isLocked()) { + secdebug("SSdb", "setting DbCommon %p timer to %d", + this, int(mParams.idleTimeout)); + Server::active().setTimer(this, Time::Interval(int(mParams.idleTimeout))); + } +} + +void KeychainDbCommon::sleepProcessing() +{ + lockDb(true); +} diff --git a/src/kcdatabase.h b/src/kcdatabase.h new file mode 100644 index 0000000..99047c8 --- /dev/null +++ b/src/kcdatabase.h @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// kcdatabase - software database container implementation. +// +// A KeychainDatabase is a software storage container, +// implemented in cooperation by the AppleCSLDP CDSA plugin and this daemon. +// +#ifndef _H_KCDATABASE +#define _H_KCDATABASE + +#include "localdatabase.h" + +class KeychainDatabase; +class KeychainDbCommon; +class KeychainKey; + + +class DbIdentifier { +public: + DbIdentifier(const DLDbIdentifier &id, DbBlob::Signature sig) + : mIdent(id), mSig(sig) { } + + const DLDbIdentifier &dlDbIdentifier() const { return mIdent; } + const DbBlob::Signature &signature() const { return mSig; } + operator const DLDbIdentifier &() const { return dlDbIdentifier(); } + operator const DbBlob::Signature &() const { return signature(); } + const char *dbName() const { return mIdent.dbName(); } + + bool operator < (const DbIdentifier &id) const // simple lexicographic + { + if (mIdent < id.mIdent) return true; + if (id.mIdent < mIdent) return false; + return mSig < id.mSig; + } + + bool operator == (const DbIdentifier &id) const + { return mIdent == id.mIdent && mSig == id.mSig; } + +private: + DLDbIdentifier mIdent; + DbBlob::Signature mSig; +}; + + +// +// KeychainDatabase DbCommons +// +class KeychainDbCommon : public DbCommon, + public DatabaseCryptoCore, public MachServer::Timer { +public: + KeychainDbCommon(Session &ssn, const DbIdentifier &id); + ~KeychainDbCommon(); + + bool unlockDb(DbBlob *blob, void **privateAclBlob = NULL); + void lockDb(bool forSleep = false); // versatile lock primitive + bool isLocked() const { return mIsLocked; } // lock status + void setUnlocked(); + + void activity(); // reset lock timeout + + void makeNewSecrets(); + + const DbIdentifier &identifier() const {return mIdentifier; } + const DLDbIdentifier &dlDbIdent() const { return identifier(); } + const char *dbName() const { return dlDbIdent().dbName(); } + + DbBlob *encode(KeychainDatabase &db); + + void notify(NotificationEvent event); + + void sleepProcessing(); + +public: + // debugging + IFDUMP(void dumpNode()); + +protected: + void action(); // timer queue action to lock keychain + +public: + DbIdentifier mIdentifier; // database external identifier [const] + // all following data locked with object lock + uint32 sequence; // change sequence number + DBParameters mParams; // database parameters (arbitrated copy) + + uint32 version; // version stamp for change tracking + +private: + bool mIsLocked; // logically locked + bool mValidParams; // mParams has been set +}; + + +// +// A Database object represents an Apple CSP/DL open database (DL/DB) object. +// It maintains its protected semantic state (including keys) and provides controlled +// access. +// +class KeychainDatabase : public LocalDatabase { + friend class KeychainDbCommon; +public: + KeychainDatabase(const DLDbIdentifier &id, const DBParameters ¶ms, Process &proc, + const AccessCredentials *cred, const AclEntryPrototype *owner); + virtual ~KeychainDatabase(); + + KeychainDbCommon &common() const; + const char *dbName() const; + +public: + static const int maxUnlockTryCount = 3; + +public: + const DbIdentifier &identifier() const { return common().identifier(); } + +public: + // encoding/decoding databases + DbBlob *blob(); + KeychainDatabase(const DLDbIdentifier &id, const DbBlob *blob, Process &proc, + const AccessCredentials *cred); + void authenticate(const AccessCredentials *cred); + void changePassphrase(const AccessCredentials *cred); + RefPointer extractMasterKey(Database &db, const AccessCredentials *cred, + const AclEntryPrototype *owner, uint32 usage, uint32 attrs); + void getDbIndex(CssmData &indexData); + + // lock/unlock processing + void lockDb(); // unconditional lock + void unlockDb(); // full-feature unlock + void unlockDb(const CssmData &passphrase); // unlock with passphrase + + bool decode(); // unlock given established master key + bool decode(const CssmData &passphrase); // set master key from PP, try unlock + + bool validatePassphrase(const CssmData &passphrase) const; // nonthrowing validation + bool isLocked() const { return common().isLocked(); } // lock status + void notify(NotificationEvent event) { return common().notify(event); } + void activity() const { common().activity(); } // reset timeout clock + + // encoding/decoding keys + void decodeKey(KeyBlob *blob, CssmKey &key, void * &pubAcl, void * &privAcl); + KeyBlob *encodeKey(const CssmKey &key, const CssmData &pubAcl, const CssmData &privAcl); + + bool validBlob() const { return mBlob && version == common().version; } + + // manage database parameters + void setParameters(const DBParameters ¶ms); + void getParameters(DBParameters ¶ms); + + // ACL state management hooks + void instantiateAcl(); + void changedAcl(); + const Database *relatedDatabase() const; // "self", for SecurityServerAcl's sake + + // debugging + IFDUMP(void dumpNode()); + +protected: + RefPointer makeKey(const CssmKey &newKey, uint32 moreAttributes, + const AclEntryPrototype *owner); + + void makeUnlocked(); // interior version of unlock() + void makeUnlocked(const AccessCredentials *cred); // like () with explicit cred + void makeUnlocked(const CssmData &passphrase); // interior version of unlock(CssmData) + + void establishOldSecrets(const AccessCredentials *creds); + void establishNewSecrets(const AccessCredentials *creds, SecurityAgent::Reason reason); + + static CssmClient::Key keyFromCreds(const TypedList &sample); + + void encode(); // (re)generate mBlob if needed + +private: + // all following data is locked by the common lock + bool mValidData; // valid ACL and params (blob decoded) + + uint32 version; // version stamp for blob validity + DbBlob *mBlob; // database blob (encoded) + + AccessCredentials *mCred; // local access credentials (always valid) +}; + +#endif //_H_KCDATABASE diff --git a/src/kckey.cpp b/src/kckey.cpp new file mode 100644 index 0000000..7c33808 --- /dev/null +++ b/src/kckey.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// key - representation of SecurityServer key objects +// +#include "kckey.h" +#include "server.h" +#include "database.h" +#include + + +// +// Create a Key object from a database-encoded blob. +// Note that this doesn't decode the blob (yet). +// +KeychainKey::KeychainKey(Database &db, const KeyBlob *blob) + : LocalKey(db) +{ + // perform basic validation on the incoming blob + assert(blob); + blob->validate(CSSMERR_APPLEDL_INVALID_KEY_BLOB); + switch (blob->version()) { +#if defined(COMPAT_OSX_10_0) + case blob->version_MacOS_10_0: + break; +#endif + case blob->version_MacOS_10_1: + break; + default: + CssmError::throwMe(CSSMERR_APPLEDL_INCOMPATIBLE_KEY_BLOB); + } + + // set it up + mBlob = blob->copy(Allocator::standard()); + mValidBlob = true; + db.addReference(*this); + secdebug("SSkey", "%p (handle 0x%lx) created from blob version %lx", + this, handle(), blob->version()); +} + + +// +// Create a Key from an explicit CssmKey. +// +KeychainKey::KeychainKey(Database &db, const CssmKey &newKey, uint32 moreAttributes, + const AclEntryPrototype *owner) + : LocalKey(db, newKey, moreAttributes, owner) +{ + assert(moreAttributes & CSSM_KEYATTR_PERMANENT); + mBlob = NULL; + mValidBlob = false; + db.addReference(*this); +} + + +KeychainKey::~KeychainKey() +{ + Allocator::standard().free(mBlob); + secdebug("SSkey", "%p destroyed", this); +} + + +KeychainDatabase &KeychainKey::database() const +{ + return referent(); +} + + +// +// Retrieve the actual CssmKey value for the key object. +// This will decode its blob if needed (and appropriate). +// +void KeychainKey::getKey() +{ + decode(); +} + +void KeychainKey::getHeader(CssmKey::Header &hdr) +{ + assert(mValidBlob); + hdr = mBlob->header; + n2hi(hdr); // correct for endian-ness +} + + +// +// Ensure that a key is fully decoded. +// This makes the mKey key value available for use, as well as its ACL. +// +void KeychainKey::decode() +{ + if (!mValidKey) { + assert(mValidBlob); // must have a blob to decode + + // decode the key + void *publicAcl, *privateAcl; + CssmKey key; + database().decodeKey(mBlob, key, publicAcl, privateAcl); + mKey = CssmClient::Key(Server::csp(), key); + importBlob(publicAcl, privateAcl); + // publicAcl points into the blob; privateAcl was allocated for us + Allocator::standard().free(privateAcl); + + // extract managed attribute bits + mAttributes = mKey.header().attributes() & managedAttributes; + mKey.header().clearAttribute(managedAttributes); + mKey.header().setAttribute(forcedAttributes); + + // key is valid now + mValidKey = true; + } +} + + +// +// Encode a key into a blob. +// We'll have to ask our Database to do this - we don't have its keys. +// Note that this returns memory we own and keep. +// +KeyBlob *KeychainKey::blob() +{ + if (!mValidBlob) { + assert(mValidKey); // must have valid key to encode + //@@@ release mBlob memory here + + // export Key ACL to blob form + CssmData pubAcl, privAcl; + exportBlob(pubAcl, privAcl); + + // assemble external key form + CssmKey externalKey = mKey; + externalKey.clearAttribute(forcedAttributes); + externalKey.setAttribute(mAttributes); + + // encode the key and replace blob + KeyBlob *newBlob = database().encodeKey(externalKey, pubAcl, privAcl); + Allocator::standard().free(mBlob); + mBlob = newBlob; + mValidBlob = true; + + // clean up and go + database().allocator.free(pubAcl); + database().allocator.free(privAcl); + } + return mBlob; +} + + +// +// Intercept ACL change requests and reset blob validity +// +void KeychainKey::instantiateAcl() +{ + decode(); +} + +void KeychainKey::changedAcl() +{ + mValidBlob = false; +} + +const Database *KeychainKey::relatedDatabase() const +{ return &database(); } diff --git a/src/kckey.h b/src/kckey.h new file mode 100644 index 0000000..9d1a259 --- /dev/null +++ b/src/kckey.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// key - representation of SecurityServer key objects +// +#ifndef _H_KCKEY +#define _H_KCKEY + +#include "localkey.h" +#include +#include + + +class KeychainDatabase; + + +// +// A Key object represents a CSSM_KEY known to the SecurityServer. +// We give each Key a handle that allows our clients to access it, while we use +// the Key's ACL to control such accesses. +// A Key can be used by multiple Connections. Whether more than one Key can represent +// the same actual key object is up to the CSP we use, so let's be tolerant about that. +// +// A note on key attributes: We keep two sets of attribute bits. The internal bits are used +// when talking to our CSP; the external bits are used when negotiating with our client(s). +// The difference is the bits in managedAttributes, which relate to persistent key storage +// and are not digestible by our CSP. The internal attributes are kept in mKey. The external +// ones are kept in mAttributes. +// +class KeychainKey : public LocalKey { +public: + KeychainKey(Database &db, const KeyBlob *blob); + KeychainKey(Database &db, const CssmKey &newKey, uint32 moreAttributes, + const AclEntryPrototype *owner = NULL); + virtual ~KeychainKey(); + + KeychainDatabase &database() const; + + // we can also yield an encoded KeyBlob *if* we belong to a database + KeyBlob *blob(); + + // ACL state management hooks + void instantiateAcl(); + void changedAcl(); + const Database *relatedDatabase() const; + CSSM_KEYATTR_FLAGS attributes() { return mAttributes; } + +private: + void decode(); + void getKey(); + virtual void getHeader(CssmKey::Header &hdr); // get header (only) without mKey + +private: + CssmKey::Header mHeaderCache; // cached, cleaned blob header cache + + KeyBlob *mBlob; // key blob encoded by mDatabase + bool mValidBlob; // mBlob is valid key encoding +}; + + +#endif //_H_KCKEY diff --git a/src/key.cpp b/src/key.cpp new file mode 100644 index 0000000..c418e36 --- /dev/null +++ b/src/key.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// key - representation of SecurityServer key objects +// +#include "key.h" +#include "server.h" +#include "database.h" +#include + + +Key::Key() + : SecurityServerAcl(keyAcl, Allocator::standard()) +{ +} + + +Database &Key::database() const +{ + return referent(); +} + + +// +// Form a KeySpec with checking and masking +// +Key::KeySpec::KeySpec(uint32 usage, uint32 attrs) + : CssmClient::KeySpec(usage, (attrs & ~managedAttributes) | forcedAttributes) +{ + if (attrs & generatedAttributes) + CssmError::throwMe(CSSMERR_CSP_INVALID_KEYATTR_MASK); +} + +Key::KeySpec::KeySpec(uint32 usage, uint32 attrs, const CssmData &label) + : CssmClient::KeySpec(usage, (attrs & ~managedAttributes) | forcedAttributes, label) +{ + if (attrs & generatedAttributes) + CssmError::throwMe(CSSMERR_CSP_INVALID_KEYATTR_MASK); +} diff --git a/src/key.h b/src/key.h new file mode 100644 index 0000000..70c4fc7 --- /dev/null +++ b/src/key.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// key - representation of SecurityServer key objects +// +#ifndef _H_KEY +#define _H_KEY + +#include "securityserver.h" +#include "structure.h" +#include "acls.h" +#include +#include + + +class Database; + + +// +// A Key object represents a CSSM_KEY known to the SecurityServer. +// We give each Key a handle that allows our clients to access it, while we use +// the Key's ACL to control such accesses. +// A Key can be used by multiple Connections. Whether more than one Key can represent +// the same actual key object is up to the CSP we use, so let's be tolerant about that. +// +// A note on key attributes: We keep two sets of attribute bits. The internal bits are used +// when talking to our CSP; the external bits are used when negotiating with our client(s). +// The difference is the bits in managedAttributes, which relate to persistent key storage +// and are not digestible by our CSP. The internal attributes are kept in mKey. The external +// ones are kept in mAttributes. +// +class Key : public PerProcess, public SecurityServerAcl { +public: + Key(); + + Database &database() const; + + virtual const CssmData &canonicalDigest() = 0; + virtual CSSM_KEYATTR_FLAGS attributes() = 0; + + virtual void returnKey(Handle &h, CssmKey::Header &hdr) = 0; + +public: + // key attributes that should not be passed on to the CSP + static const uint32 managedAttributes = KeyBlob::managedAttributes; + // these attributes are "forced on" in internal keys (but not always in external attributes) + static const uint32 forcedAttributes = KeyBlob::forcedAttributes; + // these attributes are internally generated, and invalid on input + static const uint32 generatedAttributes = + CSSM_KEYATTR_ALWAYS_SENSITIVE | CSSM_KEYATTR_NEVER_EXTRACTABLE; + + // a version of KeySpec that self-checks and masks for CSP operation + class KeySpec : public CssmClient::KeySpec { + public: + KeySpec(uint32 usage, uint32 attrs); + KeySpec(uint32 usage, uint32 attrs, const CssmData &label); + }; +}; + + +#endif //_H_KEY diff --git a/src/localdatabase.cpp b/src/localdatabase.cpp new file mode 100644 index 0000000..f486373 --- /dev/null +++ b/src/localdatabase.cpp @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// localdatabase - locally implemented database using internal CSP cryptography +// +#include "localdatabase.h" +#include "agentquery.h" +#include "localkey.h" +#include "server.h" +#include "session.h" +#include +#include // for default owner ACLs +#include +#include +#include +#include +#include +#include + + +// +// Create a Database object from initial parameters (create operation) +// +LocalDatabase::LocalDatabase(Process &proc) + : Database(proc) +{ +} + + +static inline LocalKey &myKey(Key &key) +{ + return safer_cast(key); +} + + +// +// Key inquiries +// +CSSM_KEY_SIZE LocalDatabase::queryKeySize(Key &key) +{ + CssmClient::Key theKey(Server::csp(), myKey(key)); + return theKey.sizeInBits(); +} + + +// +// Signatures and MACs +// +void LocalDatabase::generateSignature(const Context &context, Key &key, + CSSM_ALGORITHMS signOnlyAlgorithm, const CssmData &data, CssmData &signature) +{ + context.replace(CSSM_ATTRIBUTE_KEY, myKey(key).cssmKey()); + key.validate(CSSM_ACL_AUTHORIZATION_SIGN, context); + CssmClient::Sign signer(Server::csp(), context.algorithm(), signOnlyAlgorithm); + signer.override(context); + signer.sign(data, signature); +} + +void LocalDatabase::verifySignature(const Context &context, Key &key, + CSSM_ALGORITHMS verifyOnlyAlgorithm, const CssmData &data, const CssmData &signature) +{ + context.replace(CSSM_ATTRIBUTE_KEY, myKey(key).cssmKey()); + CssmClient::Verify verifier(Server::csp(), context.algorithm(), verifyOnlyAlgorithm); + verifier.override(context); + verifier.verify(data, signature); +} + +void LocalDatabase::generateMac(const Context &context, Key &key, + const CssmData &data, CssmData &mac) +{ + context.replace(CSSM_ATTRIBUTE_KEY, myKey(key).cssmKey()); + key.validate(CSSM_ACL_AUTHORIZATION_MAC, context); + CssmClient::GenerateMac signer(Server::csp(), context.algorithm()); + signer.override(context); + signer.sign(data, mac); +} + +void LocalDatabase::verifyMac(const Context &context, Key &key, + const CssmData &data, const CssmData &mac) +{ + context.replace(CSSM_ATTRIBUTE_KEY, myKey(key).cssmKey()); + key.validate(CSSM_ACL_AUTHORIZATION_MAC, context); + CssmClient::VerifyMac verifier(Server::csp(), context.algorithm()); + verifier.override(context); + verifier.verify(data, mac); +} + + +// +// Encryption/decryption +// +void LocalDatabase::encrypt(const Context &context, Key &key, + const CssmData &clear, CssmData &cipher) +{ + context.replace(CSSM_ATTRIBUTE_KEY, myKey(key).cssmKey()); + key.validate(CSSM_ACL_AUTHORIZATION_ENCRYPT, context); + CssmClient::Encrypt cryptor(Server::csp(), context.algorithm()); + cryptor.override(context); + CssmData remData; + size_t totalLength = cryptor.encrypt(clear, cipher, remData); + // shouldn't need remData - if an algorithm REQUIRES this, we'd have to ship it + if (remData) + CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); + cipher.length(totalLength); +} + +void LocalDatabase::decrypt(const Context &context, Key &key, + const CssmData &cipher, CssmData &clear) +{ + context.replace(CSSM_ATTRIBUTE_KEY, myKey(key).cssmKey()); + key.validate(CSSM_ACL_AUTHORIZATION_DECRYPT, context); + CssmClient::Decrypt cryptor(Server::csp(), context.algorithm()); + cryptor.override(context); + CssmData remData; + size_t totalLength = cryptor.decrypt(cipher, clear, remData); + // shouldn't need remData - if an algorithm REQUIRES this, we'd have to ship it + if (remData) + CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); + clear.length(totalLength); +} + + +// +// Key generation and derivation. +// Currently, we consider symmetric key generation to be fast, but +// asymmetric key generation to be (potentially) slow. +// +void LocalDatabase::generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, RefPointer &newKey) +{ + // prepare a context + CssmClient::GenerateKey generate(Server::csp(), context.algorithm()); + generate.override(context); + + // generate key + // @@@ turn "none" return into reference if permanent (only) + CssmKey key; + generate(key, Key::KeySpec(usage, attrs)); + + // register and return the generated key + newKey = makeKey(key, attrs & Key::managedAttributes, owner); +} + +void LocalDatabase::generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 pubUsage, uint32 pubAttrs, uint32 privUsage, uint32 privAttrs, + RefPointer &publicKey, RefPointer &privateKey) +{ + // prepare a context + CssmClient::GenerateKey generate(Server::csp(), context.algorithm()); + generate.override(context); + + // this may take a while; let our server object know + Server::active().longTermActivity(); + + // generate keys + // @@@ turn "none" return into reference if permanent (only) + CssmKey pubKey, privKey; + generate(pubKey, Key::KeySpec(pubUsage, pubAttrs), + privKey, Key::KeySpec(privUsage, privAttrs)); + + // register and return the generated keys + publicKey = makeKey(pubKey, pubAttrs & Key::managedAttributes, owner); + privateKey = makeKey(privKey, privAttrs & Key::managedAttributes, owner); +} + +RefPointer LocalDatabase::deriveKey(const Context &context, Key *baseKey, + const AccessCredentials *cred, const AclEntryPrototype *owner, + CssmData *param, uint32 usage, uint32 attrs) +{ + // prepare a key-derivation context + if (baseKey) { + baseKey->validate(CSSM_ACL_AUTHORIZATION_DERIVE, cred); + context.replace(CSSM_ATTRIBUTE_KEY, myKey(*baseKey).cssmKey()); + } + CssmClient::DeriveKey derive(Server::csp(), context.algorithm(), CSSM_ALGID_NONE); + derive.override(context); + + // derive key + // @@@ turn "none" return into reference if permanent (only) + CssmKey key; + derive(param, Key::KeySpec(usage, attrs), key); + + // register and return the generated key + return makeKey(key, attrs & Key::managedAttributes, owner); +} + + +// +// Key wrapping and unwrapping. +// Note that the key argument (the key in the context) is optional because of the special +// case of "cleartext" (null algorithm) wrapping for import/export. +// + +void LocalDatabase::wrapKey(const Context &context, Key *key, + Key &keyToBeWrapped, const AccessCredentials *cred, + const CssmData &descriptiveData, CssmKey &wrappedKey) +{ + keyToBeWrapped.validate(context.algorithm() == CSSM_ALGID_NONE ? + CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR : CSSM_ACL_AUTHORIZATION_EXPORT_WRAPPED, + cred); + if(!(keyToBeWrapped.attributes() & CSSM_KEYATTR_EXTRACTABLE)) { + CssmError::throwMe(CSSMERR_CSP_INVALID_KEYATTR_MASK); + } + if (key) { + context.replace(CSSM_ATTRIBUTE_KEY, myKey(*key).cssmKey()); + key->validate(CSSM_ACL_AUTHORIZATION_ENCRYPT, context); + } + CssmClient::WrapKey wrap(Server::csp(), context.algorithm()); + wrap.override(context); + wrap.cred(const_cast(cred)); //@@@ const madness - fix in client/pod + wrap(myKey(keyToBeWrapped), wrappedKey, &descriptiveData); +} + +RefPointer LocalDatabase::unwrapKey(const Context &context, Key *key, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, const CssmKey wrappedKey, + Key *publicKey, CssmData *descriptiveData) +{ + if (key) { + context.replace(CSSM_ATTRIBUTE_KEY, myKey(*key).cssmKey()); + key->validate(CSSM_ACL_AUTHORIZATION_DECRYPT, context); + } + CssmClient::UnwrapKey unwrap(Server::csp(), context.algorithm()); + unwrap.override(context); + CssmKey unwrappedKey; + unwrap.cred(const_cast(cred)); //@@@ const madness - fix in client/pod + if (owner) { + AclEntryInput ownerInput(*owner); //@@@ const trouble - fix in client/pod + unwrap.aclEntry(ownerInput); + } + + // @@@ Invoking conversion operator to CssmKey & on *publicKey and take the address of the result. + unwrap(wrappedKey, Key::KeySpec(usage, attrs), unwrappedKey, + descriptiveData, publicKey ? &static_cast(myKey(*publicKey)) : NULL); + + return makeKey(unwrappedKey, attrs & Key::managedAttributes, owner); +} + + +// +// Miscellaneous CSSM functions +// +uint32 LocalDatabase::getOutputSize(const Context &context, Key &key, uint32 inputSize, bool encrypt) +{ + // We're fudging here somewhat, since the context can be any type. + // ctx.override will fix the type, and no-one's the wiser. + context.replace(CSSM_ATTRIBUTE_KEY, myKey(key).cssmKey()); + CssmClient::Digest ctx(Server::csp(), context.algorithm()); + ctx.override(context); + return ctx.getOutputSize(inputSize, encrypt); +} + + +// +// (Re-)Authenticate the database. This changes the stored credentials. +// +void LocalDatabase::authenticate(const AccessCredentials *cred) +{ + StLock _(common()); + AccessCredentials *newCred = DataWalkers::copy(cred, Allocator::standard()); + Allocator::standard().free(mCred); + mCred = newCred; +} diff --git a/src/localdatabase.h b/src/localdatabase.h new file mode 100644 index 0000000..74553a4 --- /dev/null +++ b/src/localdatabase.h @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// localdatabase - locally implemented database using internal CSP cryptography +// +// A LocalDatabase manages keys with a locally resident AppleCSP. +// This is an abstract class useful for subclassing. +// +#ifndef _H_LOCALDATABASE +#define _H_LOCALDATABASE + +#include "database.h" + +class LocalKey; + + +// +// A Database object represents an Apple CSP/DL open database (DL/DB) object. +// It maintains its protected semantic state (including keys) and provides controlled +// access. +// +class LocalDatabase : public Database { +public: + LocalDatabase(Process &proc); + +public: + //void releaseKey(Key &key); + CSSM_KEY_SIZE queryKeySize(Key &key); + + // service calls + void generateSignature(const Context &context, Key &key, CSSM_ALGORITHMS signOnlyAlgorithm, + const CssmData &data, CssmData &signature); + void verifySignature(const Context &context, Key &key, CSSM_ALGORITHMS verifyOnlyAlgorithm, + const CssmData &data, const CssmData &signature); + void generateMac(const Context &context, Key &key, + const CssmData &data, CssmData &mac); + void verifyMac(const Context &context, Key &key, + const CssmData &data, const CssmData &mac); + + void encrypt(const Context &context, Key &key, const CssmData &clear, CssmData &cipher); + void decrypt(const Context &context, Key &key, const CssmData &cipher, CssmData &clear); + + void generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, RefPointer &newKey); + void generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 pubUsage, uint32 pubAttrs, uint32 privUsage, uint32 privAttrs, + RefPointer &publicKey, RefPointer &privateKey); + RefPointer deriveKey(const Context &context, Key *key, + const AccessCredentials *cred, const AclEntryPrototype *owner, + CssmData *param, uint32 usage, uint32 attrs); + + void wrapKey(const Context &context, Key *key, + Key &keyToBeWrapped, const AccessCredentials *cred, + const CssmData &descriptiveData, CssmKey &wrappedKey); + RefPointer unwrapKey(const Context &context, Key *key, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, const CssmKey wrappedKey, + Key *publicKey, CssmData *descriptiveData); + + uint32 getOutputSize(const Context &context, Key &key, uint32 inputSize, bool encrypt = true); + +protected: + virtual RefPointer makeKey(const CssmKey &newKey, uint32 moreAttributes, + const AclEntryPrototype *owner) = 0; + +public: + // encoding/decoding databases + void authenticate(const AccessCredentials *cred); +}; + +#endif //_H_LOCALDATABASE diff --git a/src/localkey.cpp b/src/localkey.cpp new file mode 100644 index 0000000..4550d60 --- /dev/null +++ b/src/localkey.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// localkey - Key objects that store a local CSSM key object +// +#include "localkey.h" +#include "server.h" +#include "database.h" +#include + + +// +// Create a Key from an explicit CssmKey. +// +LocalKey::LocalKey(Database &db, const CssmKey &newKey, uint32 moreAttributes, + const AclEntryPrototype *owner) + : mDigest(Server::csp().allocator()) +{ + referent(db); + mValidKey = true; + setup(newKey, moreAttributes); + + // establish initial ACL; reinterpret empty (null-list) owner as NULL for resilence's sake + if (owner && !owner->subject().empty()) + cssmSetInitial(*owner); // specified + else + cssmSetInitial(new AnyAclSubject()); // defaulted + secdebug("SSkey", "%p (handle 0x%lx) created from key alg=%ld use=0x%lx attr=0x%lx db=%p", + this, handle(), mKey.header().algorithm(), mKey.header().usage(), mAttributes, &db); +} + + +LocalKey::LocalKey(Database &db) + : mValidKey(false), mAttributes(0), mDigest(Server::csp().allocator()) +{ + referent(db); +} + + +// +// Set up the CssmKey part of this Key according to instructions. +// +void LocalKey::setup(const CssmKey &newKey, uint32 moreAttributes) +{ + mKey = CssmClient::Key(Server::csp(), newKey, false); + CssmKey::Header &header = mKey->header(); + + // copy key header + header = newKey.header(); + mAttributes = (header.attributes() & ~forcedAttributes) | moreAttributes; + + // apply initial values of derived attributes (these are all in managedAttributes) + if (!(mAttributes & CSSM_KEYATTR_EXTRACTABLE)) + mAttributes |= CSSM_KEYATTR_NEVER_EXTRACTABLE; + if (mAttributes & CSSM_KEYATTR_SENSITIVE) + mAttributes |= CSSM_KEYATTR_ALWAYS_SENSITIVE; + + // verify internal/external attribute separation + assert((header.attributes() & managedAttributes) == forcedAttributes); +} + + +LocalKey::~LocalKey() +{ + secdebug("SSkey", "%p destroyed", this); +} + + +LocalDatabase &LocalKey::database() const +{ + return referent(); +} + + +// +// Retrieve the actual CssmKey value for the key object. +// This will decode its blob if needed (and appropriate). +// +CssmClient::Key LocalKey::keyValue() +{ + if (!mValidKey) { + getKey(); + mValidKey = true; + } + return mKey; +} + + +// +// Return a key's handle and header in external form +// +void LocalKey::returnKey(Handle &h, CssmKey::Header &hdr) +{ + // return handle + h = handle(); + + // obtain the key header, from the valid key or the blob if no valid key + if (mValidKey) { + hdr = mKey.header(); + } else { + getHeader(hdr); + } + + // adjust for external attributes + hdr.clearAttribute(forcedAttributes); + hdr.setAttribute(mAttributes); +} + + +// +// Generate the canonical key digest. +// This is defined by a CSP feature that we invoke here. +// +const CssmData &LocalKey::canonicalDigest() +{ + if (!mDigest) { + CssmClient::PassThrough ctx(Server::csp()); + ctx.key(keyValue()); + CssmData *digest = NULL; + ctx(CSSM_APPLECSP_KEYDIGEST, (const void *)NULL, &digest); + assert(digest); + mDigest.set(*digest); // takes ownership of digest data + Server::csp().allocator().free(digest); // the CssmData itself + } + return mDigest.get(); +} + + +// +// Default getKey/getHeader calls - should never be called +// +void LocalKey::getKey() +{ + assert(false); +} + +void LocalKey::getHeader(CssmKey::Header &) +{ + assert(false); +} diff --git a/src/localkey.h b/src/localkey.h new file mode 100644 index 0000000..f2b47bd --- /dev/null +++ b/src/localkey.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// localkey - Key objects that store a local CSSM key object +// +#ifndef _H_LOCALKEY +#define _H_LOCALKEY + +#include "key.h" +#include +#include + + +class LocalDatabase; + + +// +// A Key object represents a CSSM_KEY known to the SecurityServer. +// We give each Key a handle that allows our clients to access it, while we use +// the Key's ACL to control such accesses. +// A Key can be used by multiple Connections. Whether more than one Key can represent +// the same actual key object is up to the CSP we use, so let's be tolerant about that. +// +// A note on key attributes: We keep two sets of attribute bits. The internal bits are used +// when talking to our CSP; the external bits are used when negotiating with our client(s). +// The difference is the bits in managedAttributes, which relate to persistent key storage +// and are not digestible by our CSP. The internal attributes are kept in mKey. The external +// ones are kept in mAttributes. +// +class LocalKey : public Key { +public: + LocalKey(Database &db, const CssmKey &newKey, uint32 moreAttributes, + const AclEntryPrototype *owner = NULL); + virtual ~LocalKey(); + + LocalDatabase &database() const; + + // yield the decoded internal key -- internal attributes + CssmClient::Key key() { return keyValue(); } + const CssmKey &cssmKey() { return keyValue(); } + operator CssmClient::Key () { return keyValue(); } + operator const CssmKey &() { return keyValue(); } + operator const CSSM_KEY & () { return keyValue(); } + + // yield the approximate external key header -- external attributes + void returnKey(Handle &h, CssmKey::Header &hdr); + + // generate the canonical key digest + const CssmData &canonicalDigest(); + + CSSM_KEYATTR_FLAGS attributes() { return mAttributes; } + +private: + void setup(const CssmKey &newKey, uint32 attrs); + CssmClient::Key keyValue(); + +protected: + LocalKey(Database &db); + + virtual void getKey(); // decode into mKey or throw + virtual void getHeader(CssmKey::Header &hdr); // get header (only) without mKey + +protected: + bool mValidKey; // CssmKey form is valid + CssmClient::Key mKey; // clear form CssmKey (attributes modified) + + CSSM_KEYATTR_FLAGS mAttributes; // full attributes (external form) + CssmAutoData mDigest; // computed key digest (cached) +}; + + +#endif //_H_LOCALKEY diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..d765ec8 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// securityd - Apple security services daemon. +// +#include + +#include "securityserver.h" +#include "server.h" +#include "entropy.h" + +#include +#include +#include "authority.h" +#include "session.h" +#include "pcscmonitor.h" + +#include +#include + +#include +#include + +#include + +// #define PERFORMANCE_MEASUREMENT 1 + +#ifdef PERFORMANCE_MEASUREMENT +#include +#endif + +// ACL subject types (their makers are instantiated here) +#include +#include +#include +#include +#include +#include +#include +#include +#include "acl_keychain.h" + + +namespace Security +{ + +// +// Program options (set by argument scan and environment) +// +uint32 debugMode = 0; +const char *bootstrapName = NULL; + +} // end namespace Security + + +// +// Local functions of the main program driver +// +static void usage(const char *me); +static void handleSIGCHLD(int); +static void handleSIGOther(int); +IFDEBUG(static void handleSIGdebug(int)); + +static Port gMainServerPort; + +// +// Main driver +// +int main(int argc, char *argv[]) +{ + #ifdef PERFORMANCE_MEASUREMENT + // needed for automated timing of securityd startup + uint64_t startTime = mach_absolute_time (); + #endif + + Debug::trace (kSecTraceSecurityServerStart); + + // program arguments (preset to defaults) + bool doFork = false; + bool forceCssmInit = false; + bool reExecute = false; + int workerTimeout = 0; + int maxThreads = 0; + const char *authorizationConfig = "/etc/authorization"; + const char *entropyFile = "/var/db/SystemEntropyCache"; + const char *equivDbFile = EQUIVALENCEDBPATH; + + // parse command line arguments + extern char *optarg; + extern int optind; + int arg; + while ((arg = getopt(argc, argv, "a:de:E:fN:t:T:X")) != -1) { + switch (arg) { + case 'a': + authorizationConfig = optarg; + break; + case 'd': + debugMode++; + break; + case 'e': + equivDbFile = optarg; + break; + case 'E': + entropyFile = optarg; + break; + case 'f': + forceCssmInit = true; + break; + case 'N': + bootstrapName = optarg; + break; + case 't': + if ((maxThreads = atoi(optarg)) < 0) + maxThreads = 0; + break; + case 'T': + if ((workerTimeout = atoi(optarg)) < 0) + workerTimeout = 0; + break; + case 'X': + doFork = true; + reExecute = true; + break; + default: + usage(argv[0]); + } + } + + // take no non-option arguments + if (optind < argc) + usage(argv[0]); + + // figure out the bootstrap name + IFDEBUG(if (!bootstrapName) bootstrapName = getenv(SECURITYSERVER_BOOTSTRAP_ENV)); + + if (!bootstrapName) { + bootstrapName = SECURITYSERVER_BOOTSTRAP_NAME; + } + + // configure logging first + if (debugMode) { + Syslog::open(bootstrapName, LOG_AUTHPRIV, LOG_PERROR); + Syslog::notice("SecurityServer started in debug mode"); + } else { + Syslog::open(bootstrapName, LOG_AUTHPRIV, LOG_CONS); + } + + // if we're not running as root in production mode, fail + // in debug mode, issue a warning + if (uid_t uid = getuid()) { +#if defined(NDEBUG) + Syslog::alert("Tried to run securityd as user %d: aborted", uid); + fprintf(stderr, "You are not allowed to run securityd\n"); + exit(1); +#else + fprintf(stderr, "securityd is unprivileged; some features may not work.\n"); + secdebug("SS", "Running as user %d (you have been warned)", uid); +#endif //NDEBUG + } + + // turn into a properly diabolical daemon unless debugMode is on + if (!debugMode) { + if (!Daemon::incarnate(doFork)) + exit(1); // can't daemonize + + if (reExecute && !Daemon::executeSelf(argv)) + exit(1); // can't self-execute + } + + // create a code signing engine + CodeSigning::OSXSigner signer; + + // create an Authorization engine + Authority authority(authorizationConfig); + + // establish the ACL machinery + new AnyAclSubject::Maker(); + new PasswordAclSubject::Maker(); + new ProtectedPasswordAclSubject::Maker(); + new ThresholdAclSubject::Maker(); + new CommentAclSubject::Maker(); + new ProcessAclSubject::Maker(); + new CodeSignatureAclSubject::Maker(signer); + new KeychainPromptAclSubject::Maker(); + + // add a temporary registration for a subject type that went out in 10.2 seed 1 + // this should probably be removed for the next major release >10.2 + new KeychainPromptAclSubject::Maker(CSSM_WORDID__RESERVED_1); + + // establish the code equivalents database + CodeSignatures codeSignatures(equivDbFile); + + // create the main server object and register it + Server server(authority, codeSignatures, bootstrapName); + + // Remember the primary service port to send signal events to. + gMainServerPort = server.primaryServicePort(); + + // set server configuration from arguments, if specified + if (workerTimeout) + server.timeout(workerTimeout); + if (maxThreads) + server.maxThreads(maxThreads); + + // add the RNG seed timer to it +# if defined(NDEBUG) + EntropyManager entropy(server, entropyFile); +# else + if (!getuid()) new EntropyManager(server, entropyFile); +# endif + + // create smartcard monitors to manage external token devices + PCSCMonitor secureCards(server); + + // create the RootSession object (if -d, give it graphics and tty attributes) + RootSession rootSession(server.primaryServicePort(), + debugMode ? (sessionHasGraphicAccess | sessionHasTTY) : 0); + + // set up signal handlers + if (signal(SIGCHLD, handleSIGCHLD) == SIG_ERR) + secdebug("SS", "Cannot handle SIGCHLD: errno=%d", errno); + if (signal(SIGINT, handleSIGOther) == SIG_ERR) + secdebug("SS", "Cannot handle SIGINT: errno=%d", errno); + if (signal(SIGTERM, handleSIGOther) == SIG_ERR) + secdebug("SS", "Cannot handle SIGTERM: errno=%d", errno); +#if !defined(NDEBUG) + if (signal(SIGUSR1, handleSIGdebug) == SIG_ERR) + secdebug("SS", "Cannot handle SIGHUP: errno=%d", errno); +#endif //NDEBUG + + // initialize CSSM now if requested + if (forceCssmInit) + server.loadCssm(); + + Syslog::notice("Entering service"); + secdebug("SS", "%s initialized", bootstrapName); + + Debug::trace (kSecTraceSecurityServerInitialized); + + #ifdef PERFORMANCE_MEASUREMENT + // needed for automated timing of securityd startup + uint64_t endTime = mach_absolute_time (); + + // compute how long it took to initialize + uint64_t elapsedTime = endTime - startTime; + mach_timebase_info_data_t multiplier; + mach_timebase_info (&multiplier); + + elapsedTime = elapsedTime * multiplier.numer / multiplier.denom; + + FILE* f = fopen ("/var/log/startuptime.txt", "a"); + if (f == NULL) + { + // probably not running as root. + f = fopen ("/tmp/startuptime.txt", "a"); + } + + fprintf (f, "%lld\n", elapsedTime); + fclose (f); + #endif + + server.run(); + + // fell out of runloop (should not happen) + Syslog::alert("Aborting"); + return 1; +} + + +// +// Issue usage message and die +// +static void usage(const char *me) +{ + fprintf(stderr, "Usage: %s [-df] [-t maxthreads] [-T threadTimeout]" + "\t[-N bootstrapName] [-a authConfigFile]\n", me); + exit(2); +} + + +// +// Handle SIGCHLD signals to reap our children (zombie cleanup) +// +static void handleSIGCHLD(int signal_number) +{ + kern_return_t kt = ucsp_client_handleSignal(gMainServerPort, mach_task_self(), signal_number); + if (kt) + syslog(LOG_ERR, "ucsp_client_handleSignal returned: %d", kt); +} + + +// +// Handle some other signals to shut down cleanly (and with logging) +// +static void handleSIGOther(int sig) +{ + switch (sig) { + case SIGINT: + //secdebug("SS", "Interrupt signal; terminating"); + Syslog::notice("received interrupt signal; terminating"); + exit(0); + case SIGTERM: + //secdebug("SS", "Termination signal; terminating"); + Syslog::notice("received termination signal; terminating"); + exit(0); + } +} + + +#if !defined(NDEBUG) + +static void handleSIGdebug(int) +{ + NodeCore::dumpAll(); +} + +#endif //NDEBUG diff --git a/src/notifications.cpp b/src/notifications.cpp new file mode 100644 index 0000000..2224dd9 --- /dev/null +++ b/src/notifications.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// EntropyManager - manage entropy on the system. +// +#include "notifications.h" +#include "server.h" +#include + + +Listener::ListenerMap Listener::listeners; +Mutex Listener::setLock; + + +Listener::Listener(Port port, NotificationDomain dom, NotificationMask evs) + : domain(dom), events(evs), mPort(port) +{ setup(); } + +Listener::Listener(NotificationDomain dom, NotificationMask evs) + : domain(dom), events(evs) +{ setup(); } + +void Listener::setup() +{ + assert(events); // what's the point? + + // register in listener set + StLock _(setLock); + listeners.insert(ListenerMap::value_type(mPort, this)); +} + +Listener::~Listener() +{ + secdebug("notify", "%p destroyed", this); +} + + +// +// Send a notification to all registered listeners +// +void Listener::notify(NotificationDomain domain, + NotificationEvent event, const CssmData &data) +{ + for (ListenerMap::const_iterator it = listeners.begin(); + it != listeners.end(); it++) { + Listener *listener = it->second; + if (listener->domain == domain && listener->wants(event)) + listener->notifyMe(domain, event, data); + } +} + + +// +// Handle a port death or deallocation by removing all Listeners using that port. +// Returns true iff we had one. +// +bool Listener::remove(Port port) +{ + assert(port); // Listeners with null ports are eternal + typedef ListenerMap::iterator Iterator; + StLock _(setLock); + pair range = listeners.equal_range(port); + if (range.first == range.second) + return false; // not one of ours + + for (Iterator it = range.first; it != range.second; it++) + delete it->second; + listeners.erase(range.first, range.second); + port.destroy(); + return true; // got it +} + + +// +// Construct a new Listener and hook it up +// +ProcessListener::ProcessListener(Process &proc, Port receiver, + NotificationDomain dom, NotificationMask evs) + : Listener(receiver, dom, evs), process(proc) +{ + // let's get told when the receiver port dies + Server::active().notifyIfDead(mPort); + + secdebug("notify", "%p created domain %ld events 0x%lx port %d", + this, domain, events, mPort.port()); +} + + +// +// Send a single notification for this listener +// +void ProcessListener::notifyMe(NotificationDomain domain, + NotificationEvent event, const CssmData &data) +{ + secdebug("notify", "%p sending domain %ld event 0x%lx to port %d process %d", + this, domain, event, mPort.port(), process.pid()); + + // send mach message (via MIG simpleroutine) + if (IFDEBUG(kern_return_t rc =) ucsp_notify_sender_notify(mPort, + MACH_SEND_TIMEOUT, 0, + domain, event, data.data(), data.length(), + 0 /*@@@ placeholder for sender ID */)) + secdebug("notify", "%p send failed (error=%d)", this, rc); +} diff --git a/src/notifications.h b/src/notifications.h new file mode 100644 index 0000000..9a0ea92 --- /dev/null +++ b/src/notifications.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// +// +#ifndef _H_NOTIFICATIONS +#define _H_NOTIFICATIONS + +#include +#include +#include +#include + +using MachPlusPlus::Port; +using SecurityServer::NotificationDomain; +using SecurityServer::NotificationEvent; +using SecurityServer::NotificationMask; + + +// +// A registered receiver of notifications. +// This is an abstract class; you must subclass to define notifyMe(). +// +// All Listeners in existence are collected in an internal map of ports to +// Listener*s, which makes them eligible to have events delivered to them via +// their notifyMe() method. There are (only) two viable lifetime management +// strategies for your Listener subclass: +// (1) Eternal: don't ever destroy your Listener. All is well. By convention, +// such Listeners use the null port. +// (2) Port-based: To get rid of your Listeners, call Listener::remove(port), +// which will delete(!) all Listeners constructed with that port. +// Except for the remove() functionality, Listener does not interpret the port. +// +// If you need another Listener lifetime management strategy, you will probably +// have to change things around here. +// +class Listener { +public: + Listener(Port port, NotificationDomain domain, NotificationMask events); + Listener(NotificationDomain domain, NotificationMask events); + virtual ~Listener(); + + // inject an event into the notification system + static void notify(NotificationDomain domain, + NotificationEvent event, const CssmData &data); + static bool remove(Port port); + + // consume an event for this Listener + virtual void notifyMe(NotificationDomain domain, + NotificationEvent event, const CssmData &data) = 0; + + const NotificationDomain domain; + const NotificationMask events; + + bool wants(NotificationEvent event) + { return (1 << event) & events; } + +protected: + Port mPort; + +private: + void setup(); + +private: + typedef multimap ListenerMap; + static ListenerMap listeners; + static Mutex setLock; +}; + + +// +// A registered receiver of notifications. +// Each one is for a particular database (or all), set of events, +// and to a particular Mach port. A process may have any number +// of listeners, each independent; so that multiple notifications can +// be sent to the same process if it registers repeatedly. +// +class Process; + +class ProcessListener : public Listener { +public: + ProcessListener(Process &proc, Port receiver, NotificationDomain domain, + NotificationMask evs = SecurityServer::kNotificationAllEvents); + + Process &process; + + void notifyMe(NotificationDomain domain, + NotificationEvent event, const CssmData &data); +}; + + +#endif //_H_NOTIFICATIONS diff --git a/src/pcsc++.cpp b/src/pcsc++.cpp new file mode 100644 index 0000000..99d9655 --- /dev/null +++ b/src/pcsc++.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// pcsc++ - PCSC client interface layer in C++ +// +#include "pcsc++.h" +#include + + +namespace Security { +namespace PCSC { + + +// +// Internal utilities +// +static void decode(vector &names, const char *buffer, size_t size) +{ + names.clear(); + for (size_t pos = 0; pos < size - 1; ) { + size_t len = strlen(buffer + pos); + names.push_back(string(buffer + pos, len)); + pos += len + 1; + } +} + +inline void decode(vector &names, const vector &buffer, size_t size) +{ + decode(names, &buffer[0], size); +} + + +// +// PCSC domain errors +// +Error::Error(long err) : error(err) +{ + IFDEBUG(debugDiagnose(this)); +} + + +const char *Error::what() const throw () +{ + return pcsc_stringify_error(error); +} + + +void Error::throwMe(long err) +{ + throw Error(err); +} + + +OSStatus Error::osStatus() const +{ + return -1; //@@@ preliminary +} + +int Error::unixError() const +{ + return EINVAL; //@@@ preliminary +} + +#if !defined(NDEBUG) +void Error::debugDiagnose(const void *id) const +{ + secdebug("exception", "%p PCSC::Error %s (%ld) osStatus %ld", + id, pcsc_stringify_error(error), error, osStatus()); +} +#endif //NDEBUG + + +// +// PodWrappers +// +void ReaderState::set(const char *name, unsigned long known) +{ + clearPod(); + szReader = name; + pvUserData = NULL; + dwCurrentState = known; +} + + +void ReaderState::lastKnown(unsigned long s) +{ + // clear out CHANGED and UNAVAILABLE + dwCurrentState = s & ~(SCARD_STATE_CHANGED | SCARD_STATE_UNAVAILABLE); +} + + +#if defined(DEBUGDUMP) + +void ReaderState::dump() +{ + Debug::dump("reader(%s) state=0x%lx events=0x%lx", + szReader ? szReader : "(null)", dwCurrentState, dwEventState); + Debug::dumpData(" ATR", rgbAtr, cbAtr); +} + +#endif //DEBUGDUMP + + +// +// Session objects +// +Session::Session() + : mIsOpen(false) +{ + open(); +} + + +Session::~Session() +{ + if (mIsOpen) + Error::check(SCardReleaseContext(mContext)); +} + + +// +// (Re)establish PCSC context. +// Don't fail on SCARD_F_INTERNAL_ERROR (pcscd not running). +void Session::open() +{ + if (!mIsOpen) { + try { + Error::check(::SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &mContext)); + mIsOpen = true; + secdebug("pcsc", "context opened"); + } catch (const Error &err) { + if (err.error == long(SCARD_F_INTERNAL_ERROR)) { + secdebug("pcsc", "context not opened"); + return; + } + } + } +} + + +bool Session::check(long rc) +{ + switch (rc) { + case SCARD_S_SUCCESS: + return true; // got reader(s), call succeeded + case SCARD_E_READER_UNAVAILABLE: + return false; // no readers, but don't treat as error + default: + Error::throwMe(rc); + return false; // placebo + } +} + + +void Session::listReaders(vector &readers, const char *groups) +{ + mReaderBuffer.resize(1000); //@@@ preliminary hack + unsigned long size = mReaderBuffer.size(); + if (check(::SCardListReaders(mContext, groups, &mReaderBuffer[0], &size))) + decode(readers, mReaderBuffer, size); + else + readers.clear(); // treat as success (returning zero readers) +} + + +// +// Reader status check +// +void Session::statusChange(ReaderState *readers, unsigned int nReaders, long timeout) +{ + if (nReaders == 0) + return; // no readers, no foul + check(::SCardGetStatusChange(mContext, timeout, readers, nReaders)); +} + + +// +// PCSC Card objects +// +Card::Card() + : mIsOpen(false) +{ +} + +Card::~Card() +{ +} + +void Card::open(Session &session, const char *reader, + unsigned long share, unsigned long protocols) +{ + Error::check(::SCardConnect(session.mContext, + reader, share, protocols, &mHandle, &mProtocol)); + mIsOpen = true; +} + +void Card::close(unsigned long disposition) +{ + if (mIsOpen) { + Error::check(::SCardDisconnect(mHandle, disposition)); + mIsOpen = false; + } +} + + +} // namespace PCSC +} // namespace Security diff --git a/src/pcsc++.h b/src/pcsc++.h new file mode 100644 index 0000000..6c839aa --- /dev/null +++ b/src/pcsc++.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// pcsc++ - PCSC client interface layer in C++ +// +// NOTE: TO BE MOVED TO security_utilities LAYER. +// +#ifndef _H_PCSC_PP +#define _H_PCSC_PP + +#include +#include +#include +#include +#include +#include + +#include + + +namespace Security { +namespace PCSC { + + +// +// PCSC-domain error exceptions +// +class Error : public CommonError { +public: + Error(long err); + + const long error; + OSStatus osStatus() const; + int unixError() const; + const char *what () const throw (); + + static void check(long err) { if (err != SCARD_S_SUCCESS) throwMe(err); } + static void throwMe(long err); + +private: + IFDEBUG(void debugDiagnose(const void *id) const); +}; + + +// +// A PODWrapper for the PCSC READERSTATE structure +// +class ReaderState : public PodWrapper { +public: + void set(const char *name, unsigned long known = SCARD_STATE_UNAWARE); + + const char *name() const { return szReader; } + void name(const char *s) { szReader = s; } + + unsigned long lastKnown() const { return dwCurrentState; } + void lastKnown(unsigned long s); + + unsigned long state() const { return dwEventState; } + bool state(unsigned long it) const { return state() & it; } + bool changed() const { return state(SCARD_STATE_CHANGED); } + + template + T * &userData() { return reinterpret_cast(pvUserData); } + + // DataOid access to the ATR data + const void *data() const { return rgbAtr; } + size_t length() const { return cbAtr; } + + IFDUMP(void dump()); +}; + + +// +// A Session represents the entire process state for the PCSC protocol +// +class Session { + friend class Card; +public: + Session(); + virtual ~Session(); + + void open(); + bool isOpen() const { return mIsOpen; } + + void listReaders(vector &readers, const char *groups = NULL); + + void statusChange(ReaderState *readers, unsigned int nReaders, + long timeout = 0); + void statusChange(vector &readers, long timeout = 0) + { statusChange(&readers[0], readers.size(), timeout); } + +private: + bool check(long rc); + +private: + bool mIsOpen; + SCARDCONTEXT mContext; + std::vector mReaderBuffer; +}; + + +// +// A Card represents a PCSC-managed card slot +// +class Card { +public: + static const unsigned long defaultProtocols = SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1; + + Card(); + ~Card(); + + void open(Session &session, const char *reader, + unsigned long share = SCARD_SHARE_SHARED, + unsigned long protocols = defaultProtocols); + void close(unsigned long disposition = SCARD_LEAVE_CARD); + +private: + bool mIsOpen; + long mHandle; + unsigned long mProtocol; +}; + + +} // namespce PCSC +} // namespace Security + + +#endif //_H_PCSC_PP diff --git a/src/pcscmonitor.cpp b/src/pcscmonitor.cpp new file mode 100644 index 0000000..bd98ce2 --- /dev/null +++ b/src/pcscmonitor.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// pcscmonitor - use PCSC to monitor smartcard reader/card state for securityd +// +// PCSCMonitor is the "glue" between PCSC and the securityd objects representing +// smartcard-related things. Its job is to translate real-world events into +// securityd-speak. +// Inputs are primarily security notifications sent from pcscd. We can also use +// MachServer-based timers to do things periodically or after some delay, though +// periodic actions are discouraged for the obvious reasons. +// +#include "pcscmonitor.h" + + +// +// Construct a PCSCMonitor. +// We strongly assume there's only one of us around here. +// +// Note that by construction, we *are* a notification Listener. +// The (unique index) port we use is zero (the null port), since we're +// not actually delivering the event anywhere (else). +// +PCSCMonitor::PCSCMonitor(Server &srv) + : Listener(kNotificationDomainPCSC, SecurityServer::kNotificationAllEvents), + server(srv) +{ + // initial reader poll + if (mSession.isOpen()) { + pollReaders(); +// server.setTimer(this, Time::Interval(5)); + + // setup complete + secdebug("sc", "pcsc monitor initialized"); + } +} + + +PCSCMonitor::~PCSCMonitor() +{ +} + + +// +// (Re)poll PCSC for smartcard status +// +void PCSCMonitor::pollReaders() +{ + // enumerate readers + vector names; // will hold reader name C strings throughout + mSession.listReaders(names); + size_t count = names.size(); + secdebug("sc", "%ld reader(s) in system", count); + + // inquire into reader state + vector states(count); // reader status array (PCSC style) + for (unsigned int n = 0; n < count; n++) { + PCSC::ReaderState &state = states[n]; + ReaderMap::iterator it = mReaders.find(names[n]); + if (it == mReaders.end()) { // new reader + state.clearPod(); + state.name(names[n].c_str()); + // lastKnown(PCSC_STATE_UNKNOWN) + // userData() = NULL + } else { + state = it->second->pcscState(); + state.name(names[n].c_str()); // OUR pointer + state.lastKnown(state.state()); + state.userData() = it->second; + } + } + mSession.statusChange(states); +#if DEBUGDUMP + if (Debug::dumping("sc")) + for (unsigned int n = 0; n < count; n++) + states[n].dump(); +#endif + + // make set of previously known reader objects (to catch those who disappeared) + set current; + copy_second(mReaders.begin(), mReaders.end(), inserter(current, current.end())); + + // match state array against them + for (unsigned int n = 0; n < count; n++) { + PCSC::ReaderState &state = states[n]; + if (Reader *reader = state.userData()) { + // if PCSC flags a change, notify the Reader + if (state.changed()) + reader->update(state); + // accounted for this reader + current.erase(reader); + } else { + RefPointer newReader = new Reader(state); + mReaders.insert(make_pair(state.name(), newReader)); + } + } + + // now deal with dead readers + for (set::iterator it = current.begin(); it != current.end(); it++) { + secdebug("sc", "killing reader %s", (*it)->name().c_str()); + (*it)->kill(); // prepare to die + mReaders.erase((*it)->name()); // remove from reader map + } +} + + +// +// Event notifier. +// These events are sent by pcscd for our (sole) benefit. +// +void PCSCMonitor::notifyMe(SecurityServer::NotificationDomain domain, + SecurityServer::NotificationEvent event, const CssmData &data) +{ + //@@@ need some kind of locking, of course... + pollReaders(); + //server.setTimer(this, Time::Interval(0.5)); + //secdebug("adhoc", "pcsc change event"); +} + + +// +// Timer action. +// +void PCSCMonitor::action() +{ + // not used at the moment; timer is not scheduled +} diff --git a/src/pcscmonitor.h b/src/pcscmonitor.h new file mode 100644 index 0000000..fee7b4c --- /dev/null +++ b/src/pcscmonitor.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// pcscmonitor - use PCSC to monitor smartcard reader/card state for securityd +// +#ifndef _H_PCSCMONITOR +#define _H_PCSCMONITOR + +#include "securityserver.h" +#include "server.h" +#include "reader.h" +#include "token.h" +#include "notifications.h" +#include "pcsc++.h" +#include + + +// +// A PCSCMonitor uses PCSC to monitor the state of smartcard readers and +// tokens (cards) in the system, and dispatches messages and events to the +// various related players in securityd. There should be at most one of these +// objects active within securityd. +// +class PCSCMonitor : private Listener, private MachServer::Timer { +public: + PCSCMonitor(Server &server); + virtual ~PCSCMonitor(); + +protected: + void pollReaders(); + + void notifyMe(SecurityServer::NotificationDomain domain, + SecurityServer::NotificationEvent event, const CssmData &data); + + void action(); + + Server &server; + +private: + PCSC::Session mSession; + + typedef map > ReaderMap; + ReaderMap mReaders; +}; + + +#endif //_H_PCSCMONITOR diff --git a/src/process.cpp b/src/process.cpp new file mode 100644 index 0000000..7dce80d --- /dev/null +++ b/src/process.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// process - track a single client process and its belongings +// +#include "process.h" +#include "server.h" +#include "session.h" +#include "tempdatabase.h" +#include "authority.h" +#include "flippers.h" + + +// +// Construct a Process object. +// +Process::Process(Port servicePort, TaskPort taskPort, + const ClientSetupInfo *info, const char *identity, uid_t uid, gid_t gid) + : mTaskPort(taskPort), mByteFlipped(false), mUid(uid), mGid(gid), + mClientIdent(deferred) +{ + // examine info passed + assert(info); + uint32 pversion = info->version; + if (pversion == SSPROTOVERSION) { + // correct protocol, same byte order, cool + } else { + Flippers::flip(pversion); + if (pversion == SSPROTOVERSION) { + // correct protocol, reversed byte order + mByteFlipped = true; + } else { + // unsupported protocol version + CssmError::throwMe(CSSM_ERRCODE_INCOMPATIBLE_VERSION); + } + } + + // set parent session + parent(Session::find(servicePort)); + + // let's take a look at our wannabe client... + mPid = mTaskPort.pid(); + + secdebug("SS", "New process %p(%d) uid=%d gid=%d session=%p TP=%d %sfor %s", + this, mPid, mUid, mGid, &session(), + mTaskPort.port(), + mByteFlipped ? "FLIP " : "", + (identity && identity[0]) ? identity : "(unknown)"); + + try { + mClientCode = CodeSigning::OSXCode::decode(identity); + } catch (...) { + secdebug("SS", "process %p(%d) identity decode threw exception", this, pid()); + } + if (!mClientCode) { + mClientIdent = unknown; // no chance to squeeze a code identity from this + secdebug("SS", "process %p(%d) no clientCode - marked anonymous", this, pid()); + } +} + + +Process::~Process() +{ + // tell all our authorizations that we're gone + IFDEBUG(if (!mAuthorizations.empty()) + secdebug("SS", "Process %p(%d) clearing %d authorizations", + this, mPid, int(mAuthorizations.size()))); + for (AuthorizationSet::iterator it = mAuthorizations.begin(); + it != mAuthorizations.end(); ) { + AuthorizationToken *auth = *it; + while (++it != mAuthorizations.end() && *it == auth) ; // Skip duplicates + if (auth->endProcess(*this)) + delete auth; + } + + // no need to lock here; the client process has no more active threads + secdebug("SS", "Process %p(%d) has died", this, mPid); + + // release our name for the process's task port + if (mTaskPort) + mTaskPort.destroy(); +} + +void Process::kill() +{ + StLock _(*this); + + // release local temp store + mLocalStore = NULL; + + // standard kill processing + PerProcess::kill(); +} + + +Session& Process::session() const +{ + return parent(); +} + + +Database &Process::localStore() +{ + StLock _(*this); + if (!mLocalStore) + mLocalStore = new TempDatabase(*this); + return *mLocalStore; +} + + +// +// Change the session of a process. +// This is the result of SessionCreate from a known process client. +// +void Process::changeSession(Port servicePort) +{ + // re-parent + parent(Session::find(servicePort)); + + secdebug("SS", "process %p(%d) changed session to %p", this, pid(), &session()); +} + + +// +// CodeSignatures implementation of Identity. +// The caller must make sure we have a valid (not necessarily hash-able) clientCode(). +// +string Process::getPath() const +{ + assert(mClientCode); + return mClientCode->canonicalPath(); +} + +const CssmData Process::getHash(CodeSigning::OSXSigner &signer) const +{ + switch (mClientIdent) { + case deferred: + try { + // try to calculate our signature hash (first time use) + mCachedSignature.reset(mClientCode->sign(signer)); + assert(mCachedSignature.get()); + mClientIdent = known; + secdebug("SS", "process %p(%d) code signature computed", this, pid()); + break; + } catch (...) { + // couldn't get client signature (unreadable, gone, hack attack, ...) + mClientIdent = unknown; + secdebug("SS", "process %p(%d) no code signature - anonymous", this, pid()); + CssmError::throwMe(CSSM_ERRCODE_INSUFFICIENT_CLIENT_IDENTIFICATION); + } + case known: + assert(mCachedSignature.get()); + break; + case unknown: + CssmError::throwMe(CSSM_ERRCODE_INSUFFICIENT_CLIENT_IDENTIFICATION); + } + return CssmData(*mCachedSignature); +} + + +// +// Authorization set maintainance +// +void Process::addAuthorization(AuthorizationToken *auth) +{ + assert(auth); + StLock _(*this); + mAuthorizations.insert(auth); + auth->addProcess(*this); +} + +void Process::checkAuthorization(AuthorizationToken *auth) +{ + assert(auth); + StLock _(*this); + if (mAuthorizations.find(auth) == mAuthorizations.end()) + MacOSError::throwMe(errAuthorizationInvalidRef); +} + +bool Process::removeAuthorization(AuthorizationToken *auth) +{ + assert(auth); + StLock _(*this); + // we do everything with a single set lookup call... + typedef AuthorizationSet::iterator Iter; + Iter it = mAuthorizations.lower_bound(auth); + bool isLast; + if (it == mAuthorizations.end() || auth != *it) { + Syslog::error("process is missing authorization to remove"); // temp. diagnostic + isLast = true; + } else { + Iter next = it; ++next; // following element + isLast = (next == mAuthorizations.end()) || auth != *next; + mAuthorizations.erase(it); // remove first match + } + if (isLast) { + if (auth->endProcess(*this)) // ... tell it to remove us, + return true; // ... and tell the caller + } + return false; // keep the auth; it's still in use +} + + +// +// Notification client maintainance +// +void Process::requestNotifications(Port port, SecurityServer::NotificationDomain domain, SecurityServer::NotificationMask events) +{ + new ProcessListener(*this, port, domain, events); +} + +void Process::stopNotifications(Port port) +{ + if (!Listener::remove(port)) + CssmError::throwMe(CSSMERR_CSSM_INVALID_HANDLE_USAGE); //@@@ bad name (should be "no such callback") +} + + +// +// Debug dump support +// +#if defined(DEBUGDUMP) + +void Process::dumpNode() +{ + PerProcess::dumpNode(); + if (mByteFlipped) + Debug::dump(" FLIPPED"); + Debug::dump(" task=%d pid=%d uid/gid=%d/%d", + mTaskPort.port(), mPid, mUid, mGid); + if (mClientCode) { + Debug::dump(" client=%s", mClientCode->canonicalPath().c_str()); + switch (mClientIdent) { + case deferred: + break; + case known: + Debug::dump("[OK]"); + break; + case unknown: + Debug::dump("[UNKNOWN]"); + break; + } + } else { + Debug::dump(" NO CLIENT ID"); + } +} + +#endif //DEBUGDUMP diff --git a/src/process.h b/src/process.h new file mode 100644 index 0000000..6b2fffe --- /dev/null +++ b/src/process.h @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// process - track a single client process and its belongings +// +#ifndef _H_PROCESS +#define _H_PROCESS + +#include "securityserver.h" +#include "structure.h" +#include +#include +#include "key.h" +#include "codesigdb.h" +#include "notifications.h" +#include + +using MachPlusPlus::Port; +using MachPlusPlus::TaskPort; + +class Session; +class LocalDatabase; +class AuthorizationToken; + + +// +// A Process object represents a UNIX process (and associated Mach Task) that has +// had contact with us and may have some state associated with it. +// +class Process : public PerProcess, public CodeSignatures::Identity { +public: + Process(Port servicePort, TaskPort tPort, + const ClientSetupInfo *info, const char *identity, + uid_t uid, gid_t gid); + virtual ~Process(); + + uid_t uid() const { return mUid; } + gid_t gid() const { return mGid; } + pid_t pid() const { return mPid; } + TaskPort taskPort() const { return mTaskPort; } + bool byteFlipped() const { return mByteFlipped; } + + CodeSigning::OSXCode *clientCode() const { return (mClientIdent == unknown) ? NULL : mClientCode; } + + void addAuthorization(AuthorizationToken *auth); + void checkAuthorization(AuthorizationToken *auth); + bool removeAuthorization(AuthorizationToken *auth); + + void kill(); + void changeSession(Port servicePort); + + void requestNotifications(Port port, NotificationDomain domain, NotificationMask events); + void stopNotifications(Port port); + + Session& session() const; + + Database &localStore(); + + // aclSequence is taken to serialize ACL validations to pick up mutual changes + Mutex aclSequence; + + IFDUMP(void dumpNode()); + +protected: + std::string getPath() const; + const CssmData getHash(CodeSigning::OSXSigner &signer) const; + +private: + // peer state: established during connection startup; fixed thereafter + TaskPort mTaskPort; // task port + bool mByteFlipped; // client's byte order is reverse of ours + pid_t mPid; // process id + uid_t mUid; // UNIX uid credential + gid_t mGid; // primary UNIX gid credential + + RefPointer mClientCode; // code object for client (NULL if unknown) + mutable enum { deferred, known, unknown } mClientIdent; // state of client identity + mutable auto_ptr mCachedSignature; // cached signature (if already known) + + // authorization dictionary + typedef multiset AuthorizationSet; + AuthorizationSet mAuthorizations; // set of valid authorizations for process + + // canonical local (transient) key store + RefPointer mLocalStore; +}; + + +// +// Convenience comparison +// +inline bool operator == (Process &p1, Process &p2) +{ + return &p1 == &p2; +} + + +#endif //_H_PROCESS diff --git a/src/reader.cpp b/src/reader.cpp new file mode 100644 index 0000000..10dd394 --- /dev/null +++ b/src/reader.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// reader - token reader objects +// +#include "reader.h" + + +Reader::Reader(const PCSC::ReaderState &state) + : mToken(NULL) +{ + mName = state.name(); + secdebug("reader", "%p (%s) new reader", this, name().c_str()); + transit(state); +} + + +Reader::~Reader() +{ + secdebug("reader", "%p (%s) destroyed", this, name().c_str()); +} + + +void Reader::kill() +{ + if (mToken) + removeToken(); + NodeCore::kill(); +} + + +void Reader::update(const PCSC::ReaderState &state) +{ + transit(state); +} + + +// +// State transition matrix for a reader, based on PCSC state changes +// +void Reader::transit(const PCSC::ReaderState &state) +{ + if (state.state(SCARD_STATE_UNAVAILABLE)) { + // reader is unusable (probably being removed) + secdebug("reader", "%p (%s) unavailable (0x%lx)", + this, name().c_str(), state.state()); + if (mToken) + removeToken(); + } else if (state.state(SCARD_STATE_EMPTY)) { + // reader is empty (no token present) + secdebug("reader", "%p (%s) empty (0x%lx)", + this, name().c_str(), state.state()); + if (mToken) + removeToken(); + } else if (state.state(SCARD_STATE_PRESENT)) { + // reader has a token inserted + secdebug("reader", "%p (%s) card present (0x%lx)", + this, name().c_str(), state.state()); + //@@@ is this hack worth it (with notifications in)?? + if (mToken && CssmData(state) != CssmData(pcscState())) + removeToken(); // incomplete but better than nothing + //@@@ or should we call some verify-still-the-same function of tokend? + if (!mToken) + insertToken(); + } else { + secdebug("reader", "%p (%s) unexpected state change (0x%ld to 0x%lx)", + this, name().c_str(), mState.state(), state.state()); + } + mState = state; +} + + +void Reader::insertToken() +{ + RefPointer token = new Token(); + token->insert(*this); + mToken = token; + addReference(*token); + secdebug("reader", "%p (%s) inserted token %p", + this, name().c_str(), mToken); +} + + +void Reader::removeToken() +{ + secdebug("reader", "%p (%s) removing token %p", + this, name().c_str(), mToken); + assert(mToken); + mToken->remove(); + removeReference(*mToken); + mToken = NULL; +} + + +// +// Debug dump support +// +#if defined(DEBUGDUMP) + +void Reader::dumpNode() +{ + PerGlobal::dumpNode(); + Debug::dump(" [%s] state=0x%lx", name().c_str(), mState.state()); +} + +#endif //DEBUGDUMP diff --git a/src/reader.h b/src/reader.h new file mode 100644 index 0000000..346f996 --- /dev/null +++ b/src/reader.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// reader - token reader objects +// +#ifndef _H_READER +#define _H_READER + +#include "securityserver.h" +#include "structure.h" +#include "token.h" +#include "pcsc++.h" + + +// +// A Reader object represents a token (card) reader device attached to the +// system. +// +class Reader : public PerGlobal { +public: + Reader(const PCSC::ReaderState &state); + ~Reader(); + + void kill(); + + string name() const { return mName; } + const PCSC::ReaderState &pcscState() const { return mState; } + + void update(const PCSC::ReaderState &state); + + IFDUMP(void dumpNode()); + +protected: + void transit(const PCSC::ReaderState &state); + void insertToken(); + void removeToken(); + +private: + string mName; // PCSC reader name + PCSC::ReaderState mState; // name field not valid (use mName) + Token *mToken; // token inserted here (also in references) +}; + + +#endif //_H_READER diff --git a/src/securityd.order b/src/securityd.order new file mode 100644 index 0000000..20bbfc2 --- /dev/null +++ b/src/securityd.order @@ -0,0 +1,265 @@ +__ZN13Authorization8RightSetD1Ev +__ZN13Authorization8RightSetD4Ev +__ZN13Authorization8RightSetD2Ev +__ZN13Authorization15MutableRightSetD4Ev +__ZN13Authorization15MutableRightSetD1Ev +__ZN6Server6handleEP17mach_msg_header_tS1_ +__Z11ucsp_serverP17mach_msg_header_tS0_ +__ZN6Server14notifyDeadNameEN8Security12MachPlusPlus4PortE +past end of text +__ZN6Server10connectionEj +__ZN6Server15requestCompleteEv +__ZN6Server15setupConnectionENS_12ConnectLevelEN8Security12MachPlusPlus4PortES3_S3_RK16security_token_tPKNS1_14SecurityServer15ClientSetupInfoEPKc +__ZN6Server10connectionEb +__ZN10Connection9checkWorkEv +__ZN7ProcessD4Ev +__ZN7Session4findEN8Security12MachPlusPlus4PortE +__ZN7ProcessC4EN8Security12MachPlusPlus4PortENS1_8TaskPortEPKNS0_14SecurityServer15ClientSetupInfoEPKcjj +__ZNK13Authorization4Rule17evaluateMechanismEPK20AuthorizationItemSetR18AuthorizationTokenRSt3setINS_10CredentialESt4lessIS7_ESaIS7_EE +__ZNK13Authorization4Rule16agentNameForAuthERK18AuthorizationToken +__ZN7Process13endConnectionER10Connection +__ZN10Connection7endWorkEv +__ZN7Process15beginConnectionER10Connection +__ZN10Connection9beginWorkEv +__ZN18AuthorizationToken4findERKN8Security14SecurityServer17AuthorizationBlobE +__Z17ucsp_server_setupjj16security_token_tPljN8Security14SecurityServer15ClientSetupInfoEPKc +__ZN13Authorization4RuleC4ERKSsPK14__CFDictionaryS5_ +__ZN7Process18checkAuthorizationEP18AuthorizationToken +__ZNK13Authorization20AuthorizationDBPlist7getRuleERKNS_5RightE +__ZN7Session16mergeCredentialsERSt3setIN13Authorization10CredentialESt4lessIS2_ESaIS2_EE +__ZN13Authorization15MutableRightSet9push_backERKNS_5RightE +__ZN13Authorization6Engine9authorizeERKNS_8RightSetEPK20AuthorizationItemSetmPKSt3setINS_10CredentialESt4lessIS8_ESaIS8_EEPSC_PNS_15MutableRightSetER18AuthorizationToken +__ZN7Session13authGetRightsERKN8Security14SecurityServer17AuthorizationBlobERKN13Authorization8RightSetEPK20AuthorizationItemSetmRNS5_15MutableRightSetE +__Z26ucsp_server_getSessionInfojj16security_token_tPlPmS1_ +__Z35ucsp_server_authorizationCopyRightsjj16security_token_tPlN8Security14SecurityServer17AuthorizationBlobEP20AuthorizationItemSetjS5_mS5_jS5_PS5_PjS6_ +__ZN10ConnectionC4ER7ProcessN8Security12MachPlusPlus4PortE +__ZN7Session10addProcessEP7Process +__ZN18AuthorizationToken16mergeCredentialsERKSt3setIN13Authorization10CredentialESt4lessIS2_ESaIS2_EE +__ZNK13Authorization4Rule21evaluateMechanismOnlyERKNS_5RightERKS0_RNS_15MutableRightSetER18AuthorizationTokenRSt3setINS_10CredentialESt4lessISB_ESaISB_EE +__ZN7Session10authCreateERKN13Authorization8RightSetEPK20AuthorizationItemSetmRN8Security14SecurityServer17AuthorizationBlobE +__ZN10Connection5abortEb +__ZN7Session13authorizationERKN8Security14SecurityServer17AuthorizationBlobE +__ZN18AuthorizationTokenC4ER7SessionRKSt3setIN13Authorization10CredentialESt4lessIS4_ESaIS4_EE +__ZNK18AuthorizationToken14effectiveCredsEv +__ZN10ConnectionC1ER7ProcessN8Security12MachPlusPlus4PortE +__ZN9XDatabaseC4ERKN8Security14DLDbIdentifierEPKNS0_14SecurityServer6DbBlobER7ProcessPKNS0_17AccessCredentialsE +__ZN7ProcessC1EN8Security12MachPlusPlus4PortENS1_8TaskPortEPKNS0_14SecurityServer15ClientSetupInfoEPKcjj +__ZN5DbKeyC4EcRKN8Security8CssmDataEbj +__ZN18AuthorizationToken10endProcessER7Process +__Z7_XsetupP17mach_msg_header_tS0_ +__ZN13Authorization5RightD1Ev +__ZN13Authorization5RightD4Ev +__ZN7Process19removeAuthorizationEP18AuthorizationToken +__ZN7Session13removeProcessEP7Process +__ZN7Session4findEm +__ZNK13Authorization4Rule12evaluateUserERKNS_5RightERKS0_RNS_15MutableRightSetEmdPKSt3setINS_10CredentialESt4lessIS9_ESaIS9_EERSD_R18AuthorizationToken +__ZN18AuthorizationToken17setCredentialInfoERKN13Authorization10CredentialE +__Z16_XgetSessionInfoP17mach_msg_header_tS0_ +__ZN18SecurityAgentQuery8activateEv +__ZN13Authorization4Rule9Attribute9getVectorEPK14__CFDictionaryPK10__CFStringb +__ZN13Authorization8RightSetC1EPK20AuthorizationItemSet +__ZN13Authorization8RightSetC4EPK20AuthorizationItemSet +__ZN13Authorization8RightSetC2EPK20AuthorizationItemSet +__ZN18DatabaseCryptoCore10decodeCoreEPN8Security14SecurityServer6DbBlobEPPv +__ZN18DatabaseCryptoCore10makeRawKeyEPvmmm +__ZNK13Authorization4Rule26evaluateCredentialForRightERKNS_5RightERKS0_PK20AuthorizationItemSetdRKNS_10CredentialEb +__ZN14CodeSignatures7addLinkERKN8Security8CssmDataES3_PKcb +__ZN13Authorization4RuleD4Ev +__ZN14CodeSignatures8makeLinkERNS_8IdentityERKSsbj +__ZN13Authorization20AuthorizationDBPlist4syncEd +__ZN13Authorization15MutableRightSetC4EmRKNS_5RightE +__ZN13Authorization15MutableRightSetC1EmRKNS_5RightE +__ZN13Authorization5RightC1Ev +__ZN7ProcessD0Ev +__ZN13Authorization5RightC4Ev +__ZN17AuthorizationItemC2Ev +__ZN17AuthorizationItemC4Ev +__Z31ucsp_server_authorizationCreatejj16security_token_tPlP20AuthorizationItemSetjS2_mS2_jS2_PN8Security14SecurityServer17AuthorizationBlobE +__Z20ucsp_server_decodeDbjj16security_token_tPlPmPN8Security11DataWalkers18DLDbFlatIdentifierEjS5_PNS2_17AccessCredentialsEjS7_Pvj +__ZN13Authorization15MutableRightSet4growEm +__ZN13Authorization10CredentialD4Ev +__ZN13Authorization10CredentialD1Ev +__ZN13Authorization5RightC1EPKcmPKv +__ZN13Authorization5RightC4EPKcmPKv +__Z20ucsp_server_isLockedjj16security_token_tPlmPi +__ZN14CodeSignatures8IdentityC2Ev +__ZN14CodeSignatures8IdentityC4Ev +__ZN18AuthorizationToken10addProcessER7Process +__ZN7Process16addAuthorizationEP18AuthorizationToken +__ZN18SecurityAgentQuery9terminateEv +__Z29__MIG_check__Request__setup_tP18__Request__setup_t +__ZN7Session15authExternalizeERKN8Security14SecurityServer17AuthorizationBlobER25AuthorizationExternalForm +__Z38__MIG_check__Request__getSessionInfo_tP27__Request__getSessionInfo_t +__ZN26CheckingReconstituteWalkerC4EPvS0_mb +__Z20ucsp_server_setupNewjj16security_token_tPljN8Security14SecurityServer15ClientSetupInfoEPKcPj +__ZN18AuthorizationToken7DeleterC4ERKN8Security14SecurityServer17AuthorizationBlobE +__ZN7Session8authFreeERKN8Security14SecurityServer17AuthorizationBlobEm +__ZN14DynamicSessionC4ERKN8Security12MachPlusPlus9BootstrapE +__ZN13Authorization15MutableRightSetC4ERKNS_8RightSetE +__ZN13Authorization15MutableRightSetC1ERKNS_8RightSetE +__ZN7Process4killEb +__ZN20QueryInvokeMechanismclERKSsS1_PK24AuthorizationValueVectorRKN13Authorization8RightSetES8_PmRP20AuthorizationItemSetSC_ +__ZNK13Authorization14CredentialImplltERKS0_ +__Z30ucsp_server_addCodeEquivalencejj16security_token_tPlPvjS1_jPKci +__ZN18AuthorizationToken7infoSetEv +__ZN10ConnectionD0Ev +__ZN10ConnectionD4Ev +__ZN7SessionC4EN8Security12MachPlusPlus9BootstrapENS1_4PortEm +__ZN14CodeSignatures8IdentityD2Ev +__ZN14CodeSignatures8IdentityD4Ev +__ZN5DbKeyC1EcRKN8Security8CssmDataEbj +__ZN13Authorization4RuleD1Ev +__Z10_XisLockedP17mach_msg_header_tS0_ +__ZNK13Authorization4Rule8evaluateERKNS_5RightERKS0_RNS_15MutableRightSetEmdPKSt3setINS_10CredentialESt4lessIS9_ESaIS9_EERSD_R18AuthorizationToken +__Z25_XauthorizationCopyRightsP17mach_msg_header_tS0_ +__ZN13Authorization4RuleC1ERKSsPK14__CFDictionaryS5_ +__ZN18AuthorizationTokenD4Ev +__Z32ucsp_server_authorizationReleasejj16security_token_tPlN8Security14SecurityServer17AuthorizationBlobEm +__ZN7Process11addDatabaseEP9XDatabase +__ZN7Session15authInternalizeERK25AuthorizationExternalFormRN8Security14SecurityServer17AuthorizationBlobE +__ZN7Process14removeDatabaseEP9XDatabase +__ZN9XDatabaseD4Ev +__ZN7Session16addAuthorizationEP18AuthorizationToken +__ZNK13Authorization14CredentialImpl7isValidEv +__ZN13Authorization20AuthorizationDBPlist4loadEv +__ZN18AuthorizationToken10setInfoSetER20AuthorizationItemSet +__ZN13Authorization4Rule9Attribute7getBoolEPK14__CFDictionaryPK10__CFStringbb +__ZN7Session19removeAuthorizationEP18AuthorizationToken +__ZN13Authorization4Rule9Attribute9getStringEPK14__CFDictionaryPK10__CFStringbPc +__Z36ucsp_server_authorizationExternalizejj16security_token_tPlN8Security14SecurityServer17AuthorizationBlobEP25AuthorizationExternalForm +__ZN20QueryInvokeMechanismC1EjRK18AuthorizationTokenPKc +__ZN18SecurityAgentQueryD4Ev +__ZN20QueryInvokeMechanismC4EjRK18AuthorizationTokenPKc +__ZN18SecurityAgentQueryC2EjR7SessionPKc +__ZN18SecurityAgentQueryC4EjR7SessionPKc +__ZN18AuthorizationToken7DeleterC1ERKN8Security14SecurityServer17AuthorizationBlobE +__ZNK18DatabaseCryptoCore17deriveDbMasterKeyERKN8Security8CssmDataE +__ZN18AuthorizationTokenC1ER7SessionRKSt3setIN13Authorization10CredentialESt4lessIS4_ESaIS4_EE +__ZNK13Authorization14CredentialImpl8isSharedEv +__Z21_XauthorizationCreateP17mach_msg_header_tS0_ +__Z22_XauthorizationReleaseP17mach_msg_header_tS0_ +__Z23ucsp_server_setupThreadjj16security_token_tPlj +__Z33ucsp_server_authorizationCopyInfojj16security_token_tPlN8Security14SecurityServer17AuthorizationBlobEPKcPP20AuthorizationItemSetPjS8_ +__ZN7Session11authGetInfoERKN8Security14SecurityServer17AuthorizationBlobEPKcRP20AuthorizationItemSet +__ZN7Session14clearResourcesEv +__ZN8Listener6removeEN8Security12MachPlusPlus4PortE +__ZN8ListenerC4ER7ProcessN8Security12MachPlusPlus4PortEmm +__ZN6Server8loadCssmEv +__Z47__MIG_check__Request__authorizationCopyRights_tP36__Request__authorizationCopyRights_t +__ZN13Authorization4Rule9Attribute19getLocalizedPromptsEPK14__CFDictionaryRSt3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE +__Z32__MIG_check__Request__isLocked_tP21__Request__isLocked_t +__ZN13Authorization15MutableRightSet4swapERS0_ +__ZN9XDatabaseC1ERKN8Security14DLDbIdentifierEPKNS0_14SecurityServer6DbBlobER7ProcessPKNS0_17AccessCredentialsE +__Z36ucsp_server_authorizationInternalizejj16security_token_tPl25AuthorizationExternalFormPN8Security14SecurityServer17AuthorizationBlobE +__Z20_XaddCodeEquivalenceP17mach_msg_header_tS0_ +__ZN8Listener6notifyEmmRKN8Security8CssmDataE +__Z10_XdecodeDbP17mach_msg_header_tS0_ +__Z21ucsp_server_releaseDbjj16security_token_tPlm +__Z26_XauthorizationExternalizeP17mach_msg_header_tS0_ +_main +__ZN18AuthorizationToken7Deleter6removeEv +__ZN18DatabaseCryptoCoreC4Ev +__ZN18AuthorizationTokenD1Ev +__ZN18AuthorizationToken14mayInternalizeER7Processb +__ZN7Session9eliminateEN8Security12MachPlusPlus4PortE +__ZN13Authorization14CredentialImpl5mergeERKS0_ +__ZN9XDatabase6Common6unlockEPN8Security14SecurityServer6DbBlobEPPv +__ZN13Authorization14CredentialImplC4ERKSsS2_b +__ZN9XDatabase6Common8activityEv +__ZN6ServerC4ER9AuthorityR14CodeSignaturesPKc +__ZN7SessionC2EN8Security12MachPlusPlus9BootstrapENS1_4PortEm +__Z44__MIG_check__Request__authorizationRelease_tP33__Request__authorizationRelease_t +__Z43__MIG_check__Request__authorizationCreate_tP32__Request__authorizationCreate_t +__ZN9XDatabaseD0Ev +__Z11_XreleaseDbP17mach_msg_header_tS0_ +__ZN14DynamicSessionC1ERKN8Security12MachPlusPlus9BootstrapE +__Z26_XauthorizationInternalizeP17mach_msg_header_tS0_ +__Z24ucsp_server_setupSessionjj16security_token_tPlmm +__Z10_XsetupNewP17mach_msg_header_tS0_ +__ZN9XDatabase6CommonC4ERKNS_12DbIdentifierERNS_9CommonMapE +__ZN9XDatabase6CommonC1ERKNS_12DbIdentifierERNS_9CommonMapE +__ZN18DatabaseCryptoCoreC2Ev +__Z14_XsetupSessionP17mach_msg_header_tS0_ +__Z34ucsp_server_unlockDbWithPassphrasejj16security_token_tPlmPvj +__ZN7Session5setupEmm +__ZN7Session15setupAttributesEm +__Z48__MIG_check__Request__authorizationExternalize_tP37__Request__authorizationExternalize_t +__ZN14CodeSignatures8Identity13canonicalNameERKSs +__Z32__MIG_check__Request__decodeDb_tP21__Request__decodeDb_t +__ZN13Authorization4Rule9Attribute9getDoubleEPK14__CFDictionaryPK10__CFStringbd +__Z42__MIG_check__Request__addCodeEquivalence_tP31__Request__addCodeEquivalence_tPS0_ +__ZN8Listener8notifyMeEmmRKN8Security8CssmDataE +__ZNK18AuthorizationToken14mayExternalizeER7Process +__ZN14EntropyManager17updateEntropyFileEv +__ZNK13Authorization14CredentialImpl12creationTimeEv +__ZN9XDatabase6decodeEv +__ZNK8Security14SecurityServer10CommonBlob8validateEl +__ZNK8Security14SecurityServer10CommonBlob7isValidEv +__Z13_XsetupThreadP17mach_msg_header_tS0_ +__ZN9XDatabase6unlockERKN8Security8CssmDataE +__Z23_XauthorizationCopyInfoP17mach_msg_header_tS0_ +__Z31ucsp_server_requestNotificationjj16security_token_tPljmm +__ZN16KeychainNotifier6notifyERKN8Security14DLDbIdentifierEi +__ZN18DatabaseCryptoCore5setupEPKN8Security14SecurityServer6DbBlobERKNS0_8CssmDataE +__ZN14DynamicSessionD4Ev +__ZN9XDatabase16lockAllDatabasesERNS_9CommonMapEb +__ZN9XDatabase6CommonD4Ev +__ZN13Authorization14CredentialImplC1ERKSsS2_b +__ZN13Authorization10CredentialC4ERKSsS2_b +__ZN13Authorization10CredentialC1ERKSsS2_b +__ZN20QueryInvokeMechanism14terminateAgentEv +__ZN9XDatabase6decodeERKN8Security8CssmDataE +__ZN9XDatabase12makeUnlockedERKN8Security8CssmDataE +__ZN17SecurityServerAclD2Ev +__Z36__MIG_check__Request__setupSession_tP25__Request__setupSession_t +__ZN17SecurityServerAclD4Ev +__Z32__MIG_check__Request__setupNew_tP21__Request__setupNew_t +__ZN13Authorization20AuthorizationDBPlist11parseConfigEP14__CFDictionary +__Z48__MIG_check__Request__authorizationInternalize_tP37__Request__authorizationInternalize_t +__ZN14EntropyManagerC4ERN8Security12MachPlusPlus10MachServerEPKc +__Z33__MIG_check__Request__releaseDb_tP22__Request__releaseDb_t +__ZN14EntropyManager6actionEv +__ZN16KeychainNotifier6unlockERKN8Security14DLDbIdentifierE +__ZN7SessionD4Ev +__Z21_XrequestNotificationP17mach_msg_header_tS0_ +__ZN11RootSessionC4EN8Security12MachPlusPlus4PortEm +__Z24_XunlockDbWithPassphraseP17mach_msg_header_tS0_ +__Z7initMdsv +__Z45__MIG_check__Request__authorizationCopyInfo_tP34__Request__authorizationCopyInfo_t +__Z13handleSIGCHLDi +__Z35__MIG_check__Request__setupThread_tP24__Request__setupThread_t +__ZN13Authorization20AuthorizationDBPlistC4EPKc +__ZN18DatabaseCryptoCoreD4Ev +__Z28ucsp_server_stopNotificationjj16security_token_tPlj +__ZN6Server15notifyNoSendersEN8Security12MachPlusPlus4PortEj +__ZN7Process20requestNotificationsEN8Security12MachPlusPlus4PortEmm +__ZN8ListenerC1ER7ProcessN8Security12MachPlusPlus4PortEmm +__ZN11RootSessionC1EN8Security12MachPlusPlus4PortEm +__Z46__MIG_check__Request__unlockDbWithPassphrase_tP35__Request__unlockDbWithPassphrase_t +__ZN14EntropyManagerC1ERN8Security12MachPlusPlus10MachServerEPKc +__Z43__MIG_check__Request__requestNotification_tP32__Request__requestNotification_t +__ZN6ServerC1ER9AuthorityR14CodeSignaturesPKc +__ZN18DatabaseCryptoCoreD2Ev +__ZN7Process17stopNotificationsEN8Security12MachPlusPlus4PortE +__ZN7SessionD2Ev +__ZN14DynamicSession7releaseEv +__Z18_XstopNotificationP17mach_msg_header_tS0_ +__ZN14DynamicSessionD0Ev +__Z40__MIG_check__Request__stopNotification_tP29__Request__stopNotification_t +__ZN13Authorization20AuthorizationDBPlistC1EPKc +__ZN9XDatabase6CommonD0Ev +__ZN9XDatabase7discardEPNS_6CommonE +__ZN13Authorization14CredentialImpl10invalidateEv +__ZN13Authorization6EngineC4EPKc +__ZN8ListenerD4Ev +__ZN8ListenerD0Ev +__Z14handleSIGOtheri +__ZN13Authorization6EngineC2EPKc +__ZN9AuthorityC1EPKc +__ZN9AuthorityC4EPKc +__ZN14CodeSignaturesC1EPKc +__ZN14EntropyManager14collectEntropyEv +__ZN14CodeSignaturesC4EPKc +_ucsp_notify_sender_notify +__ZN6Server3runEv +__ZN13Authorization14CredentialImplD4Ev diff --git a/src/securityserver.h b/src/securityserver.h new file mode 100644 index 0000000..3c71207 --- /dev/null +++ b/src/securityserver.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// securityserver.h - master header file for the SecurityServer. +// +#ifndef _H_SECURITYSERVER +#define _H_SECURITYSERVER + +#include +#include +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include + +namespace Security { + +using namespace SecurityServer; +using namespace UnixPlusPlus; + + +// +// Logging and verbosity levels +// +extern uint32 debugMode; +extern const char *bootstrapName; + +} // end namespace Security + +#endif //_H_SECURITYSERVER diff --git a/src/server.cpp b/src/server.cpp new file mode 100644 index 0000000..af82dd7 --- /dev/null +++ b/src/server.cpp @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// server - the actual SecurityServer server object +// +#include // knowledge of mig message sizes +#include "server.h" +#include "session.h" +#include "acls.h" +#include "notifications.h" +#include + +using namespace MachPlusPlus; + +// +// Construct an Authority +// +Authority::Authority(const char *configFile) +: Authorization::Engine(configFile) +{ +} + +Authority::~Authority() +{ +} + +// +// Construct the server object +// +Server::Server(Authority &authority, CodeSignatures &signatures, const char *bootstrapName) + : MachServer(bootstrapName), + mBootstrapName(bootstrapName), + mCSPModule(gGuidAppleCSP, mCssm), mCSP(mCSPModule), + mAuthority(authority), + mCodeSignatures(signatures) +{ + // engage the subsidiary port handler for sleep notifications + add(sleepWatcher); +} + + +// +// Clean up the server object +// +Server::~Server() +{ + //@@@ more later +} + + +// +// Locate a connection by reply port and make it the current connection +// of this thread. The connection will be marked busy, and can be accessed +// by calling Server::connection() [no argument] until it is released by +// calling Connection::endWork(). +// +Connection &Server::connection(mach_port_t port) +{ + Server &server = active(); + StLock _(server); + Connection *conn = server.mConnections.get(port, CSSM_ERRCODE_INVALID_CONTEXT_HANDLE); + active().mCurrentConnection() = conn; + conn->beginWork(); + return *conn; +} + +Connection &Server::connection(bool tolerant) +{ + Connection *conn = active().mCurrentConnection(); + assert(conn); // have to have one + if (!tolerant) + conn->checkWork(); + return *conn; +} + +void Server::requestComplete() +{ + // note: there may not be an active connection if connection setup failed + if (RefPointer &conn = active().mCurrentConnection()) { + conn->endWork(); + conn = NULL; + } + IFDUMPING("state", NodeCore::dumpAll()); +} + + +// +// Shorthand for "current" process and session. +// This is the process and session for the current connection. +// +Process &Server::process() +{ + return connection().process(); +} + +Session &Server::session() +{ + return connection().process().session(); +} + +RefPointer Server::key(KeyHandle key) +{ + return HandleObject::findRef(key, CSSMERR_CSP_INVALID_KEY_REFERENCE); +} + +RefPointer Server::database(DbHandle db) +{ + RefPointer database = + HandleObject::findRef(db, CSSMERR_DL_INVALID_DB_HANDLE); + if (database->process() != process()) + CssmError::throwMe(CSSMERR_DL_INVALID_DB_HANDLE); + return database; +} + +RefPointer Server::keychain(DbHandle db) +{ + RefPointer keychain = + HandleObject::findRef(db, CSSMERR_DL_INVALID_DB_HANDLE); + if (keychain->process() != process()) + CssmError::throwMe(CSSMERR_DL_INVALID_DB_HANDLE); + return keychain; +} + +RefPointer Server::optionalDatabase(DbHandle db) +{ + if (db == noDb) + return &process().localStore(); + else + return database(db); +} + + +// +// Locate an ACL bearer (database or key) by handle +// +SecurityServerAcl &Server::aclBearer(AclKind kind, CSSM_HANDLE handle) +{ + SecurityServerAcl &bearer = + HandleObject::find(handle, CSSMERR_CSSM_INVALID_ADDIN_HANDLE); + if (kind != bearer.kind()) + CssmError::throwMe(CSSMERR_CSSM_INVALID_HANDLE_USAGE); + return bearer; +} + + +// +// Run the server. This will not return until the server is forced to exit. +// +void Server::run() +{ + MachServer::run(0x10000, + MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | + MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_SENDER)); +} + + +// +// The primary server run-loop function. +// Invokes the MIG-generated main dispatch function (ucsp_server). +// For debug builds, look up request names in a MIG-generated table +// for better debug-log messages. +// +boolean_t ucsp_server(mach_msg_header_t *, mach_msg_header_t *); + +#if defined(NDEBUG) + +boolean_t Server::handle(mach_msg_header_t *in, mach_msg_header_t *out) +{ + return ucsp_server(in, out); +} + +#else //NDEBUG + +static const struct IPCName { const char *name; int ipc; } ipcNames[] = + { subsystem_to_name_map_ucsp }; // macro generated by MIG, from ucsp.h + +boolean_t Server::handle(mach_msg_header_t *in, mach_msg_header_t *out) +{ + const int first = ipcNames[0].ipc; + const char *name = (in->msgh_id >= first && in->msgh_id < first + ucsp_MSG_COUNT) ? + ipcNames[in->msgh_id - first].name : "OUT OF BOUNDS"; + secdebug("SSreq", "begin %s (%d)", name, in->msgh_id); + boolean_t result = ucsp_server(in, out); + secdebug("SSreq", "end %s (%d)", name, in->msgh_id); + return result; +} + +#endif //NDEBUG + + +// +// Set up a new Connection. This establishes the environment (process et al) as needed +// and registers a properly initialized Connection object to run with. +// Type indicates how "deep" we need to initialize (new session, process, or connection). +// Everything at and below that level is constructed. This is straight-forward except +// in the case of session re-initialization (see below). +// +void Server::setupConnection(ConnectLevel type, Port servicePort, Port replyPort, Port taskPort, + const security_token_t &securityToken, const ClientSetupInfo *info, const char *identity) +{ + // first, make or find the process based on task port + StLock _(*this); + RefPointer &proc = mProcesses[taskPort]; + if (type == connectNewSession && proc) { + // The client has talked to us before and now wants to create a new session. + proc->changeSession(servicePort); + } + if (!proc) { + if (type == connectNewThread) // client error (or attack) + CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); + assert(info && identity); + proc = new Process(servicePort, taskPort, info, identity, + securityToken.val[0], securityToken.val[1]); + notifyIfDead(taskPort); + } + + // now, establish a connection and register it in the server + Connection *connection = new Connection(*proc, replyPort); + if (mConnections.contains(replyPort)) // malicious re-entry attempt? + CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); //@@@ error code? (client error) + mConnections[replyPort] = connection; + notifyIfDead(replyPort); +} + + +// +// Synchronously end a Connection. +// This is due to a request from the client, so no thread races are possible. +// In practice, this is optional since the DPN for the client thread reply port +// will destroy the connection anyway when the thread dies. +// +void Server::endConnection(Port replyPort) +{ + StLock _(*this); + PortMap::iterator it = mConnections.find(replyPort); + assert(it != mConnections.end()); + it->second->terminate(); + mConnections.erase(it); +} + + +// +// Handling dead-port notifications. +// This receives DPNs for all kinds of ports we're interested in. +// +void Server::notifyDeadName(Port port) +{ + StLock _(*this); + secdebug("SSports", "port %d is dead", port.port()); + + // is it a connection? + PortMap::iterator conIt = mConnections.find(port); + if (conIt != mConnections.end()) { + conIt->second->abort(); + mConnections.erase(conIt); + return; + } + + // is it a process? + PortMap::iterator procIt = mProcesses.find(port); + if (procIt != mProcesses.end()) { + procIt->second->kill(); + mProcesses.erase(procIt); + return; + } + + // is it a notification client? + if (Listener::remove(port)) + return; + + secdebug("server", "spurious dead port notification for port %d", port.port()); +} + + +// +// Handling no-senders notifications. +// This is currently only used for (subsidiary) service ports +// +void Server::notifyNoSenders(Port port, mach_port_mscount_t) +{ + secdebug("SSports", "port %d no senders", port.port()); + Session::destroy(port); +} + + +// +// Notifier for system sleep events +// +void Server::SleepWatcher::systemWillSleep() +{ + secdebug("SS", "sleep notification received"); + Session::processSystemSleep(); +} + + +// +// Return the primary Cryptographic Service Provider. +// This will be lazily loaded when it is first requested. +// +CssmClient::CSP &Server::getCsp() +{ + if (!mCssm->isActive()) + loadCssm(); + return mCSP; +} + + +// +// Initialize the CSSM/MDS subsystem. +// This is thread-safe and can be done lazily. +// +static void initMds(); + +void Server::loadCssm() +{ + if (!mCssm->isActive()) { + StLock _(*this); + if (!mCssm->isActive()) { + try { + initMds(); + } catch (const CssmError &error) { + switch (error.osStatus()) { + case CSSMERR_DL_MDS_ERROR: + case CSSMERR_DL_OS_ACCESS_DENIED: + secdebug("SS", "MDS initialization failed; continuing"); + Syslog::warning("MDS initialization failed; continuing"); + break; + default: + throw; + } + } + secdebug("SS", "CSSM initializing"); + mCssm->init(); + mCSP->attach(); + IFDEBUG(char guids[Guid::stringRepLength+1]); + secdebug("SS", "CSSM ready with CSP %s", mCSP->guid().toString(guids)); + } + } +} + +#include + +static void initMds() +{ + secdebug("SS", "MDS initializing"); + CssmAllocatorMemoryFunctions memory(Allocator::standard()); + MDS_FUNCS functions; + MDS_HANDLE handle; + CssmError::check(MDS_Initialize(NULL, &memory, &functions, &handle)); + CssmError::check(MDS_Install(handle)); + CssmError::check(MDS_Terminate(handle)); +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..64a1373 --- /dev/null +++ b/src/server.h @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// server - the actual Server object +// +#ifndef _H_SERVER +#define _H_SERVER + +#include "securityserver.h" +#include "structure.h" +#include +#include +#include +#include +#include +#include +#include +#include "codesigdb.h" +#include "connection.h" +#include "key.h" +#include "database.h" +#include "localdatabase.h" +#include "kcdatabase.h" +#include "authority.h" +#include "AuthorizationEngine.h" +#include + +#define EQUIVALENCEDBPATH "/var/db/CodeEquivalenceDatabase" + +// +// The authority itself. You will usually only have one of these. +// +class Authority : public Authorization::Engine { +public: + Authority(const char *configFile); + ~Authority(); +}; + +class Server : public PerGlobal, + public MachPlusPlus::MachServer, + public UniformRandomBlobs { +public: + Server(Authority &myAuthority, CodeSignatures &signatures, const char *bootstrapName); + ~Server(); + + // run the server until it shuts down + void run(); + + // + // Retrieve pieces of the Server's object web. + // These are all static methods that use the active() Server of this thread. + // + static Server &active() { return safer_cast(MachServer::active()); } + static const char *bootstrapName() { return active().mBootstrapName.c_str(); } + + static Connection &connection(mach_port_t replyPort); + static Connection &connection(bool tolerant = false); + static void requestComplete(); + + static Process &process(); + static Session &session(); + + static RefPointer key(KeyHandle key); + static RefPointer optionalKey(KeyHandle k) { return (k == noKey) ? NULL : key(k); } + static RefPointer database(DbHandle db); + static RefPointer keychain(DbHandle db); + static RefPointer optionalDatabase(DbHandle db); + static Authority &authority() { return active().mAuthority; } + static CodeSignatures &codeSignatures() { return active().mCodeSignatures; } + static SecurityServerAcl &aclBearer(AclKind kind, CSSM_HANDLE handle); + static CssmClient::CSP &csp() { return active().getCsp(); } + + void loadCssm(); + +public: + // set up a new connection + enum ConnectLevel { + connectNewSession, + connectNewProcess, + connectNewThread + }; + void setupConnection(ConnectLevel type, Port servicePort, Port replyPort, Port taskPort, + const security_token_t &securityToken, + const ClientSetupInfo *info = NULL, const char *executablePath = NULL); + + void endConnection(Port replyPort); + + static void releaseWhenDone(Allocator &alloc, void *memory) + { MachServer::active().releaseWhenDone(alloc, memory); } + static void releaseWhenDone(void *memory) + { releaseWhenDone(Allocator::standard(), memory); } + +protected: + // implementation methods of MachServer + boolean_t handle(mach_msg_header_t *in, mach_msg_header_t *out); + void notifyDeadName(Port port); + void notifyNoSenders(Port port, mach_port_mscount_t); + +private: + class SleepWatcher : public MachPlusPlus::PortPowerWatcher { + public: + void systemWillSleep(); + }; + SleepWatcher sleepWatcher; + +private: + // mach bootstrap registration name + std::string mBootstrapName; + + // connection map (by client reply port) + PortMap mConnections; + + // process map (by process task port) + PortMap mProcesses; + + // Current connection, if any (per thread). + // Set as a side effect of calling connection(mach_port_t) + // and returned by connection(bool). + ThreadNexus > mCurrentConnection; + + // CSSM components + CssmClient::Cssm mCssm; // CSSM instance + CssmClient::Module mCSPModule; // CSP module + CssmClient::CSP mCSP; // CSP attachment + CssmClient::CSP &getCsp(); // lazily initialize, then return CSP attachment + + Authority &mAuthority; + CodeSignatures &mCodeSignatures; +}; + +#endif //_H_SERVER diff --git a/src/session.cpp b/src/session.cpp new file mode 100644 index 0000000..98c2762 --- /dev/null +++ b/src/session.cpp @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// session - authentication session domains +// +// A Session is defined by a mach_init bootstrap dictionary. These dictionaries are +// hierarchical and inherited, so they work well for characterization of processes +// that "belong" together. (Of course, if your mach_init is broken, you're in bad shape.) +// +// Sessions are multi-threaded objects. +// +#include "session.h" +#include "connection.h" +#include "database.h" +#include "server.h" + + +// +// The static session map +// +PortMap Session::mSessions; + + +// +// Create a Session object from initial parameters (create) +// +Session::Session(Bootstrap bootstrap, Port servicePort, SessionAttributeBits attrs) + : mBootstrap(bootstrap), mServicePort(servicePort), + mAttributes(attrs), mDying(false) +{ + secdebug("SSsession", "%p CREATED: handle=0x%lx bootstrap=%d service=%d attrs=0x%lx", + this, handle(), mBootstrap.port(), mServicePort.port(), mAttributes); +} + + +void Session::release() +{ + // nothing by default +} + + +// +// The root session inherits the startup bootstrap and service port +// +RootSession::RootSession(Port servicePort, SessionAttributeBits attrs) + : Session(Bootstrap(), servicePort, sessionIsRoot | sessionWasInitialized | attrs) +{ + ref(); // eternalize + + // self-install (no thread safety issues here) + mSessions[mServicePort] = this; +} + + +// +// Dynamic sessions use the given bootstrap and re-register in it +// +DynamicSession::DynamicSession(const Bootstrap &bootstrap) + : ReceivePort(Server::active().bootstrapName(), bootstrap), + Session(bootstrap, *this) +{ + // tell the server to listen to our port + Server::active().add(*this); + + // register for port notifications + Server::active().notifyIfDead(bootstrapPort()); //@@@??? still needed? + Server::active().notifyIfUnused(*this); + + // self-register + StLock _(mSessions); + assert(!mSessions[*this]); // can't be registered already (we just made it) + mSessions[*this] = this; +} + +DynamicSession::~DynamicSession() +{ + // remove our service port from the server + Server::active().remove(*this); +} + + +void DynamicSession::release() +{ + mBootstrap.destroy(); +} + + +// +// Destroy a Session +// +Session::~Session() +{ + secdebug("SSsession", "%p DESTROYED: handle=0x%lx bootstrap=%d", + this, handle(), mBootstrap.port()); +} + + +// +// Locate a session object by service port or (Session API) identifier +// +Session &Session::find(Port servicePort) +{ + StLock _(mSessions); + PortMap::const_iterator it = mSessions.find(servicePort); + assert(it != mSessions.end()); + return *it->second; +} + +Session &Session::find(SecuritySessionId id) +{ + switch (id) { + case callerSecuritySession: + return Server::session(); + default: + return HandleObject::find(id, CSSMERR_CSSM_INVALID_ADDIN_HANDLE); + } +} + + +// +// Act on a death notification for a session's (sub)bootstrap port. +// We may not destroy the Session outright here (due to processes that use it), +// but we do clear out its accumulated wealth. +// +void Session::destroy(Port servPort) +{ + // remove session from session map + StLock _(mSessions); + PortMap::iterator it = mSessions.find(servPort); + assert(it != mSessions.end()); + Session *session = it->second; + session->kill(); + mSessions.erase(it); +} + +void Session::kill() +{ + release(); + + StLock _(mLock); + + // this session is now officially dying + mDying = true; + + // invalidate shared credentials + { + StLock _(mCredsLock); + + IFDEBUG(if (!mSessionCreds.empty()) + secdebug("SSauth", "session %p clearing %d shared credentials", + this, int(mSessionCreds.size()))); + for (CredentialSet::iterator it = mSessionCreds.begin(); it != mSessionCreds.end(); it++) + (*it)->invalidate(); + } + + // base kill processing + PerSession::kill(); +} + + +// +// Relay lockAllDatabases to all known sessions +// +void Session::processSystemSleep() +{ + StLock _(mSessions); + for (PortMap::const_iterator it = mSessions.begin(); it != mSessions.end(); it++) + it->second->allReferences(&DbCommon::sleepProcessing); +} + + +// +// Authorization operations +// +OSStatus Session::authCreate(const AuthItemSet &rights, + const AuthItemSet &environment, + AuthorizationFlags flags, + AuthorizationBlob &newHandle, + const security_token_t &securityToken) +{ + // invoke the authorization computation engine + CredentialSet resultCreds; + + // this will acquire mLock, so we delay acquiring it + auto_ptr auth(new AuthorizationToken(*this, resultCreds, securityToken)); + + // Make a copy of the mSessionCreds + CredentialSet sessionCreds; + { + StLock _(mCredsLock); + sessionCreds = mSessionCreds; + } + + AuthItemSet outRights; + OSStatus result = Server::authority().authorize(rights, environment, flags, + &sessionCreds, &resultCreds, outRights, *auth); + newHandle = auth->handle(); + + // merge resulting creds into shared pool + if ((flags & kAuthorizationFlagExtendRights) && + !(flags & kAuthorizationFlagDestroyRights)) + { + StLock _(mCredsLock); + mergeCredentials(resultCreds); + auth->mergeCredentials(resultCreds); + } + + // Make sure that this isn't done until the auth(AuthorizationToken) is guaranteed to + // not be destroyed anymore since it's destructor asserts it has no processes + Server::process().addAuthorization(auth.get()); + auth.release(); + return result; +} + +void Session::authFree(const AuthorizationBlob &authBlob, AuthorizationFlags flags) +{ + AuthorizationToken::Deleter deleter(authBlob); + AuthorizationToken &auth = deleter; + Process &process = Server::process(); + process.checkAuthorization(&auth); + + if (flags & kAuthorizationFlagDestroyRights) { + // explicitly invalidate all shared credentials and remove them from the session + for (CredentialSet::const_iterator it = auth.begin(); it != auth.end(); it++) + if ((*it)->isShared()) + (*it)->invalidate(); + } + + // now get rid of the authorization itself + if (process.removeAuthorization(&auth)) + deleter.remove(); +} + +OSStatus Session::authGetRights(const AuthorizationBlob &authBlob, + const AuthItemSet &rights, const AuthItemSet &environment, + AuthorizationFlags flags, + AuthItemSet &grantedRights) +{ + CredentialSet resultCreds; + AuthorizationToken &auth = authorization(authBlob); + CredentialSet effective; + { + StLock _(mCredsLock); + effective = auth.effectiveCreds(); + } + OSStatus result = Server::authority().authorize(rights, environment, flags, + &effective, &resultCreds, grantedRights, auth); + + // merge resulting creds into shared pool + if ((flags & kAuthorizationFlagExtendRights) && !(flags & kAuthorizationFlagDestroyRights)) + { + StLock _(mCredsLock); + mergeCredentials(resultCreds); + auth.mergeCredentials(resultCreds); + } + + secdebug("SSauth", "Authorization %p copyRights asked for %d got %d", + &authorization(authBlob), int(rights.size()), int(grantedRights.size())); + return result; +} + +OSStatus Session::authGetInfo(const AuthorizationBlob &authBlob, + const char *tag, + AuthItemSet &contextInfo) +{ + AuthorizationToken &auth = authorization(authBlob); + secdebug("SSauth", "Authorization %p get-info", &auth); + contextInfo = auth.infoSet(tag); + return noErr; +} + +OSStatus Session::authExternalize(const AuthorizationBlob &authBlob, + AuthorizationExternalForm &extForm) +{ + const AuthorizationToken &auth = authorization(authBlob); + StLock _(mLock); + if (auth.mayExternalize(Server::process())) { + memset(&extForm, 0, sizeof(extForm)); + AuthorizationExternalBlob &extBlob = + reinterpret_cast(extForm); + extBlob.blob = auth.handle(); + extBlob.session = bootstrapPort(); + secdebug("SSauth", "Authorization %p externalized", &auth); + return noErr; + } else + return errAuthorizationExternalizeNotAllowed; +} + +OSStatus Session::authInternalize(const AuthorizationExternalForm &extForm, + AuthorizationBlob &authBlob) +{ + // interpret the external form + const AuthorizationExternalBlob &extBlob = + reinterpret_cast(extForm); + + // locate source authorization + AuthorizationToken &sourceAuth = AuthorizationToken::find(extBlob.blob); + + // check for permission and do it + if (sourceAuth.mayInternalize(Server::process(), true)) { + StLock _(mLock); + authBlob = extBlob.blob; + Server::process().addAuthorization(&sourceAuth); + secdebug("SSauth", "Authorization %p internalized", &sourceAuth); + return noErr; + } else + return errAuthorizationInternalizeNotAllowed; +} + + +// +// Set up a (new-ish) Session. +// This call must be made from a process within the session, and it must be the first +// such process to make the call. +// +void Session::setup(SessionCreationFlags flags, SessionAttributeBits attrs) +{ + // check current process object - it may have been cached before the client's bootstrap switch + Process *process = &Server::process(); + process->session().setupAttributes(attrs); +} + + +void Session::setupAttributes(SessionAttributeBits attrs) +{ + secdebug("SSsession", "%p setup attrs=0x%lx", this, attrs); + if (attrs & ~settableAttributes) + MacOSError::throwMe(errSessionInvalidAttributes); + if (attribute(sessionWasInitialized)) + MacOSError::throwMe(errSessionAuthorizationDenied); + setAttributes(attrs | sessionWasInitialized); +} + + +OSStatus Session::authorizationdbGet(AuthorizationString inRightName, CFDictionaryRef *rightDict) +{ + string rightName(inRightName); + return Server::authority().getRule(rightName, rightDict); +} + + +OSStatus Session::authorizationdbSet(const AuthorizationBlob &authBlob, AuthorizationString inRightName, CFDictionaryRef rightDict) +{ + CredentialSet resultCreds; + AuthorizationToken &auth = authorization(authBlob); + CredentialSet effective; + + { + StLock _(mCredsLock); + effective = auth.effectiveCreds(); + } + + OSStatus result = Server::authority().setRule(inRightName, rightDict, &effective, &resultCreds, auth); + + { + StLock _(mCredsLock); + mergeCredentials(resultCreds); + auth.mergeCredentials(resultCreds); + } + + secdebug("SSauth", "Authorization %p authorizationdbSet %s (result=%ld)", + &authorization(authBlob), inRightName, result); + return result; +} + + +OSStatus Session::authorizationdbRemove(const AuthorizationBlob &authBlob, AuthorizationString inRightName) +{ + CredentialSet resultCreds; + AuthorizationToken &auth = authorization(authBlob); + CredentialSet effective; + + { + StLock _(mCredsLock); + effective = auth.effectiveCreds(); + } + + OSStatus result = Server::authority().removeRule(inRightName, &effective, &resultCreds, auth); + + { + StLock _(mCredsLock); + mergeCredentials(resultCreds); + auth.mergeCredentials(resultCreds); + } + + secdebug("SSauth", "Authorization %p authorizationdbRemove %s (result=%ld)", + &authorization(authBlob), inRightName, result); + return result; +} + + +// +// Merge a set of credentials into the shared-session credential pool +// +// must hold mCredsLock +void Session::mergeCredentials(CredentialSet &creds) +{ + secdebug("SSsession", "%p merge creds @%p", this, &creds); + for (CredentialSet::const_iterator it = creds.begin(); it != creds.end(); it++) + if (((*it)->isShared() && (*it)->isValid())) { + CredentialSet::iterator old = mSessionCreds.find(*it); + if (old == mSessionCreds.end()) { + mSessionCreds.insert(*it); + } else { + // replace "new" with "old" in input set to retain synchronization + (*old)->merge(**it); + creds.erase(it); + creds.insert(*old); + } + } +} + + +// +// Locate an AuthorizationToken given a blob +// +AuthorizationToken &Session::authorization(const AuthorizationBlob &blob) +{ + AuthorizationToken &auth = AuthorizationToken::find(blob); + Server::process().checkAuthorization(&auth); + return auth; +} + + +// +// Debug dumping +// +#if defined(DEBUGDUMP) + +void Session::dumpNode() +{ + PerSession::dumpNode(); + if (mDying) + Debug::dump(" DYING"); + Debug::dump(" boot=%d service=%d attrs=0x%lx", + mBootstrap.port(), mServicePort.port(), mAttributes); +} + +#endif //DEBUGDUMP diff --git a/src/session.h b/src/session.h new file mode 100644 index 0000000..b9950c2 --- /dev/null +++ b/src/session.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// session - authentication session domains +// +#ifndef _H_SESSION +#define _H_SESSION + +#include "securityserver.h" +#include "structure.h" +#include "acls.h" +#include "authority.h" +#include +#include +#include + +#if __GNUC__ > 2 +#include +using __gnu_cxx::hash_map; +#else +#include +#endif + + +class Key; +class Connection; + + +// +// A Session object represents one or more Connections that are known to +// belong to the same authentication domain. Informally this means just +// about "the same user", for the right definition of "user." The upshot +// is that global credentials can be shared by Connections of one Session +// with a modicum of security, and so Sessions are the natural nexus of +// single-sign-on functionality. +// +class Session : public HandleObject, public PerSession { +public: + typedef MachPlusPlus::Bootstrap Bootstrap; + + Session(Bootstrap bootstrap, Port servicePort, SessionAttributeBits attrs = 0); + virtual ~Session(); + + Bootstrap bootstrapPort() const { return mBootstrap; } + Port servicePort() const { return mServicePort; } + + virtual void release(); + + IFDUMP(virtual void dumpNode()); + +public: + static const SessionAttributeBits settableAttributes = + sessionHasGraphicAccess | sessionHasTTY | sessionIsRemote; + + SessionAttributeBits attributes() const { return mAttributes; } + bool attribute(SessionAttributeBits bits) const { return mAttributes & bits; } + + static void setup(SessionCreationFlags flags, SessionAttributeBits attrs); + void setupAttributes(SessionAttributeBits attrs); + +protected: + void setAttributes(SessionAttributeBits attrs) { mAttributes |= attrs; } + +public: + const CredentialSet &authCredentials() const { return mSessionCreds; } + + OSStatus authCreate(const AuthItemSet &rights, const AuthItemSet &environment, + AuthorizationFlags flags, AuthorizationBlob &newHandle, const security_token_t &securityToken); + void authFree(const AuthorizationBlob &auth, AuthorizationFlags flags); + OSStatus authGetRights(const AuthorizationBlob &auth, + const AuthItemSet &requestedRights, const AuthItemSet &environment, + AuthorizationFlags flags, AuthItemSet &grantedRights); + OSStatus authGetInfo(const AuthorizationBlob &auth, const char *tag, AuthItemSet &contextInfo); + + OSStatus authExternalize(const AuthorizationBlob &auth, AuthorizationExternalForm &extForm); + OSStatus authInternalize(const AuthorizationExternalForm &extForm, AuthorizationBlob &auth); + + OSStatus authorizationdbGet(AuthorizationString inRightName, CFDictionaryRef *rightDict); + OSStatus authorizationdbSet(const AuthorizationBlob &authBlob, AuthorizationString inRightName, CFDictionaryRef rightDict); + OSStatus authorizationdbRemove(const AuthorizationBlob &authBlob, AuthorizationString inRightName); + +private: + struct AuthorizationExternalBlob { + AuthorizationBlob blob; + mach_port_t session; + }; + +protected: + AuthorizationToken &authorization(const AuthorizationBlob &blob); + void mergeCredentials(CredentialSet &creds); + +public: + static Session &find(Port servPort); + static Session &find(SecuritySessionId id); + static void destroy(Port servPort); + + static void processSystemSleep(); + +protected: + mutable Mutex mLock; // object lock + + Bootstrap mBootstrap; // session bootstrap port + Port mServicePort; // SecurityServer service port for this session + SessionAttributeBits mAttributes; // attribute bits (see AuthSession.h) + bool mDying; // session is dying + + mutable Mutex mCredsLock; // lock for mSessionCreds + CredentialSet mSessionCreds; // shared session authorization credentials + + void kill(); + +private: + static PortMap mSessions; +}; + + +// +// The RootSession is the session (i.e. bootstrap dictionary) of system daemons that are +// started early and don't belong to anything more restrictive. The RootSession is considered +// immortal. +// Currently, telnet sessions et al also default into this session, but this will change +// (we hope). +// +class RootSession : public Session { +public: + RootSession(Port servicePort, SessionAttributeBits attrs = 0); +}; + + +// +// A DynamicSession is the default type of session object. We create one when a new +// Connection initializes whose bootstrap port we haven't seen before. These Sessions +// are torn down when their bootstrap object disappears (which happens when mach_init +// destroys it due to its requestor referent vanishing). +// +class DynamicSession : private ReceivePort, public Session { +public: + DynamicSession(const Bootstrap &bootstrap); + ~DynamicSession(); + +protected: + void release(); +}; + + +#endif //_H_SESSION diff --git a/src/structure.cpp b/src/structure.cpp new file mode 100644 index 0000000..4d6dd36 --- /dev/null +++ b/src/structure.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// structure - structural framework for securityd objects +// +#include "structure.h" + + +// +// NodeCore always has a destructor (because it's virtual), +// but its dump support is conditionally included. +// +NodeCore::~NodeCore() +{ +#if defined(DEBUGDUMP) + StLock _(mCoreLock); + mCoreNodes.erase(this); +#endif //DEBUGDUMP +} + + +// +// ClearReferences clears the reference set but does not propagate +// anything; it is NOT recursive. +// +void NodeCore::clearReferences() +{ + StLock _(*this); + secdebug("ssnode", "%p clearing all %d references", + this, int(mReferences.size())); + mReferences.erase(mReferences.begin(), mReferences.end()); +} + + +// +// Kill should be overloaded by Nodes to implement any cleanup and release +// operations that should happen at LOGICAL death of the represented object. +// This is where you should release ports, close files, etc. +// This default behavior, which you MUST include in your override, +// propagates kills to all active references, recursively. +// +void NodeCore::kill() +{ + StLock _(*this); + for (ReferenceSet::const_iterator it = mReferences.begin(); it != mReferences.end(); it++) + (*it)->kill(); + clearReferences(); +} + + +// +// NodeCore-level support for state dumping. +// Call NodeCore::dumpAll() to debug-dump all nodes. +// Note that enabling DEBUGDUMP serializes all node creation/destruction +// operations, and thus may cause significant shifts in thread interactions. +// +#if defined(DEBUGDUMP) + +// The (uncounted) set of all known NodeCores in existence, with protective lock +set NodeCore::mCoreNodes; +Mutex NodeCore::mCoreLock; + +// add a new NodeCore to the known set +NodeCore::NodeCore() + : Mutex(Mutex::recursive) +{ + StLock _(mCoreLock); + mCoreNodes.insert(this); +} + +// partial-line common dump text for any NodeCore +// override this to add text to your Node type's state dump output +void NodeCore::dumpNode() +{ + Debug::dump("%s@%p rc=%u", Debug::typeName(*this).c_str(), this, unsigned(refCountForDebuggingOnly())); + if (mParent) + Debug::dump(" parent=%p", mParent.get()); + if (mReferent) + Debug::dump(" referent=%p", mReferent.get()); +} + +// full-line dump of a NodeCore +// override this to completely re-implement the dump format for your Node type +void NodeCore::dump() +{ + dumpNode(); + if (!mReferences.empty()) { + Debug::dump(" {"); + for (ReferenceSet::const_iterator it = mReferences.begin(); it != mReferences.end(); it++) + Debug::dump(" %p", it->get()); + Debug::dump(" }"); + } + Debug::dump("\n"); +} + +// dump all known nodes +void NodeCore::dumpAll() +{ + StLock _(mCoreLock); + time_t now; time(&now); + Debug::dump("\nNODE DUMP (%24.24s)\n", ctime(&now)); + for (set::const_iterator it = mCoreNodes.begin(); it != mCoreNodes.end(); it++) + (*it)->dump(); + Debug::dump("END NODE DUMP\n\n"); +} + +#endif //DEBUGDUMP diff --git a/src/structure.h b/src/structure.h new file mode 100644 index 0000000..f5b61bf --- /dev/null +++ b/src/structure.h @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// structure - structural framework for securityd objects +// +#ifndef _H_STRUCTURE +#define _H_STRUCTURE + +#include "securityserver.h" +#include +#include + + +// +// Track a per-process real world object +// +template class Node; +class PerConnection; +class PerProcess; +class PerSession; +class PerGlobal; + + +// +// A generic core node of the object mesh. +// Repeat after me: "Everything that matters is a Node." +// +// This contains the mesh links (as smart pointers to NodeCores). +// The 'parent' is the next-more-global related object in the mesh, if any; +// nodes with the same parent "belong together" at the more global layer. +// For example, processes have their sessions as parents. +// The 'referent' is an object at the *same* globality layer that controls +// the lifetime of this node. For example, a Database has its Process as +// its referent. +// Both parent and referent are optional (can be NULL). +// The references set is a partial referent back-link. All NodeCores listed +// in a node's References have this node as a referent, but the set is +// selective (not necessarily complete). The References set propagates the +// 'kill' operation up the referents chain; thus being included in a node's +// References means that a kill() on the referent will (recursively) kill +// all references, too. +// +// Do not inherit directly from NodeCore; use Node<> (below). +// +class NodeCore : public RefCount, public Mutex { + template friend class Node; +public: +#if !defined(DEBUGDUMP) // (see below if DEBUGDUMP) + NodeCore() : Mutex(Mutex::recursive) { } +#endif + virtual ~NodeCore(); + + bool hasParent() const { return mParent; } + bool hasReferent() const { return mReferent; } + + // reference set operations + template + void allReferences(void (Sub::*func)()); + template + Sub *findFirst(Value (Sub::*func)() const, Value compare); + void clearReferences(); + + virtual void kill(); // always invoke NodeCore's in your override + + // for STL ordering (so we can have sets of RefPointers of NodeCores) + bool operator < (const NodeCore &other) const + { return this < &other; } + +private: + RefPointer mParent; + RefPointer mReferent; + typedef set > ReferenceSet; + ReferenceSet mReferences; + +#if defined(DEBUGDUMP) +public: // dump support + NodeCore(); // dump-only constructor (registers node) + + virtual void dumpNode(); // node description (partial line) + virtual void dump(); // dumpNode() + references + NL + static void dumpAll(); // dump all nodes + + static Mutex mCoreLock; // lock for mCoreNodes + static set mCoreNodes; // (debug) set of all known nodes +#endif //DEBUGDUMP +}; + + +template +void NodeCore::allReferences(void (Sub::*func)()) +{ + StLock _(*this); + for (ReferenceSet::const_iterator it = mReferences.begin(); it != mReferences.end(); it++) + if (Sub *sub = dynamic_cast(it->get())) + (sub->*func)(); +} + +template +Sub *NodeCore::findFirst(Value (Sub::*func)() const, Value compare) +{ + StLock _(*this); + for (ReferenceSet::const_iterator it = mReferences.begin(); it != mReferences.end(); it++) + if (Sub *sub = dynamic_cast(it->get())) + if ((sub->*func)() == compare) + return sub; + return NULL; +} + + +// +// A typed node of the object mesh. +// This adds type-safe accessors and modifiers to NodeCore. +// +template +class Node : public NodeCore { +protected: + void parent(Glob &p) + { StLock _(*this); mParent = &p; } + + virtual void referent(Base &r) + { StLock _(*this); mReferent = &r; } + + void clearReferent() + { + StLock _(*this); + mReferent = NULL; + } + +public: + template + T& parent() const + { assert(mParent); return safer_cast(*mParent); } + + template + T& referent() const + { assert(mReferent); return safer_cast(*mReferent); } + +public: + void addReference(Base &p) + { StLock _(*this); assert(p.mReferent == this); mReferences.insert(&p); } + void removeReference(Base &p) { StLock _(*this); mReferences.erase(&p); } +}; + + +// +// Connection (client thread) layer nodes +// +class PerConnection : public Node { +public: +}; + + +// +// Process (client process) layer nodes +// +class PerProcess : public HandleObject, public Node { +public: +}; + + +// +// Session (client-side session) layer nodes +// +class PerSession : public Node { +public: +}; + + +// +// Global (per-system) layer nodes +// +class PerGlobal : public Node { +public: +}; + + +// +// A map from mach port names to (refcounted) pointers-to-somethings +// +template +class PortMap : public Mutex, public std::map > { + typedef std::map > _Map; +public: + bool contains(mach_port_t port) const { return find(port) != end(); } + Node *getOpt(mach_port_t port) const + { + typename _Map::const_iterator it = find(port); + return (it == end()) ? NULL : it->second; + } + + Node *get(mach_port_t port) const + { + typename _Map::const_iterator it = find(port); + assert(it != end()); + return it->second; + } + + Node *get(mach_port_t port, OSStatus error) const + { + typename _Map::const_iterator it = find(port); + if (it == end()) + MacOSError::throwMe(error); + return it->second; + } + + void dump(); +}; + +template +void PortMap::dump() +{ + for (typename _Map::const_iterator it = begin(); it != end(); it++) + it->second->dump(); +} + + +#endif //_H_STRUCTURE diff --git a/src/tempdatabase.cpp b/src/tempdatabase.cpp new file mode 100644 index 0000000..4d64dae --- /dev/null +++ b/src/tempdatabase.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// tempdatabase - temporary (scratch) storage for keys +// +#include +#include +#include +#include "tempdatabase.h" +#include "localkey.h" +#include "server.h" +#include "session.h" +#include "agentquery.h" + + +class TempKey : public LocalKey { +public: + TempKey(Database &db, const CssmKey &newKey, uint32 moreAttributes, + const AclEntryPrototype *owner = NULL); +}; + + + +TempKey::TempKey(Database &db, const CssmKey &newKey, uint32 moreAttributes, + const AclEntryPrototype *owner) + : LocalKey(db, newKey, moreAttributes, owner) +{ + secdebug("SS adhoc", "Creating temporary (local) key"); // XXX/gh + db.addReference(*this); +} + + +// +// Create a Database object from initial parameters (create operation) +// +TempDatabase::TempDatabase(Process &proc) + : LocalDatabase(proc) +{ + proc.addReference(*this); +} + + +// +// A LocalDatabase itself doesn't really have a database name, +// but here's an innocent placeholder. +// +const char *TempDatabase::dbName() const +{ + return "(transient)"; +} + + +// +// Invoke the Security Agent to get a passphrase (other than for a Keychain) +// +void TempDatabase::getSecurePassphrase(const Context &context, + string &passphrase) +{ + uint32 verify = context.getInt(CSSM_ATTRIBUTE_VERIFY_PASSPHRASE, CSSMERR_CSSM_ATTRIBUTE_NOT_IN_CONTEXT); + + CssmData *promptData = context.get(CSSM_ATTRIBUTE_PROMPT); + const char *prompt = NULL; + + if (promptData) + prompt = *promptData; + + QueryGenericPassphrase agentQuery; + agentQuery.inferHints(Server::process()); + agentQuery(prompt, verify, passphrase); +} + + +void TempDatabase::makeSecurePassphraseKey(const Context &context, + const AccessCredentials *cred, + const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, + RefPointer &newKey) +{ + secdebug("SSdb", "requesting secure passphrase"); + + string passphrase; + getSecurePassphrase(context, passphrase); + + secdebug("SSdb", "wrapping securely-obtained passphrase as key"); + + // CssmKey rawKey(StringData(passphrase)) confuses gcc + StringData passphraseData(passphrase); + CssmKey rawKey(passphraseData); + rawKey.algorithm(context.algorithm()); + rawKey.blobType(CSSM_KEYBLOB_RAW); + rawKey.blobFormat(CSSM_KEYBLOB_WRAPPED_FORMAT_NONE); + rawKey.keyClass(CSSM_KEYCLASS_SESSION_KEY); + + CssmClient::UnwrapKey unwrap(Server::csp(), CSSM_ALGID_NONE); + CssmKey cspKey; + unwrap(rawKey, Key::KeySpec(usage, attrs), cspKey); + + newKey = makeKey(cspKey, attrs & Key::managedAttributes, owner); +} + + +// +// Obtain "secure passphrases" for the CSP. Useful for PKCS 12. +// +void TempDatabase::generateKey(const Context &context, + const AccessCredentials *cred, + const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, + RefPointer &newKey) +{ + switch (context.algorithm()) + { + case CSSM_ALGID_SECURE_PASSPHRASE: + makeSecurePassphraseKey(context, cred, owner, usage, attrs, newKey); + break; + default: + LocalDatabase::generateKey(context, cred, owner, usage, attrs, newKey); + return; + } +} + + +// +// Make a new TempKey +// +RefPointer TempDatabase::makeKey(const CssmKey &newKey, + uint32 moreAttributes, const AclEntryPrototype *owner) +{ + return new TempKey(*this, newKey, moreAttributes, owner); +} diff --git a/src/tempdatabase.h b/src/tempdatabase.h new file mode 100644 index 0000000..7d3b50e --- /dev/null +++ b/src/tempdatabase.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// tempdatabase - temporary (scratch) storage for keys +// +// A TempDatabase locally manages keys using the AppleCSP while providing +// no persistent storage. Keys live until they are no longer referenced in +// client space, at which point they are destroyed. +// +#ifndef _H_TEMPDATABASE +#define _H_TEMPDATABASE + +#include "localdatabase.h" + + +// +// A Database object represents an Apple CSP/DL open database (DL/DB) object. +// It maintains its protected semantic state (including keys) and provides controlled +// access. +// +class TempDatabase : public LocalDatabase { +public: + TempDatabase(Process &proc); + + const char *dbName() const; + + void generateKey(const Context &context, + const AccessCredentials *cred, + const AclEntryPrototype *owner, uint32 usage, + uint32 attrs, RefPointer &newKey); + +protected: + void getSecurePassphrase(const Context &context, string &passphrase); + void makeSecurePassphraseKey(const Context &context, const AccessCredentials *cred, + const AclEntryPrototype *owner, uint32 usage, + uint32 attrs, RefPointer &newKey); + + RefPointer makeKey(const CssmKey &newKey, uint32 moreAttributes, + const AclEntryPrototype *owner); +}; + +#endif //_H_TEMPDATABASE diff --git a/src/token.cpp b/src/token.cpp new file mode 100644 index 0000000..9772864 --- /dev/null +++ b/src/token.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// token - internal representation of a (single distinct) hardware token +// +#include "token.h" +#include "reader.h" +#include "notifications.h" + + +Token::Token() +{ + secdebug("token", "%p created", this); +} + + +Token::~Token() +{ + secdebug("token", "%p destroyed", this); +} + + +Reader &Token::reader() const +{ + return referent(); +} + + +void Token::insert(Reader &slot) +{ + referent(slot); + mState = slot.pcscState(); + //@@@ pupulate MDS here + notify(kNotificationCDSAInsertion); +} + + +void Token::remove() +{ + //@@@ clear MDS here + notify(kNotificationCDSARemoval); + clearReferent(); +} + + +// +// Send CDSA-layer notifications for this token. +// These events are usually received by CDSA plugins working with securityd. +// @@@ Need to add CDSA identifier as data +// +void Token::notify(NotificationEvent event) +{ + Listener::notify(kNotificationDomainCDSA, event, CssmData()); +} + + +// +// Debug dump support +// +#if defined(DEBUGDUMP) + +void Token::dumpNode() +{ + PerGlobal::dumpNode(); +} + +#endif //DEBUGDUMP diff --git a/src/token.h b/src/token.h new file mode 100644 index 0000000..b487146 --- /dev/null +++ b/src/token.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// token - internal representation of a (single distinct) hardware token +// +#ifndef _H_TOKEN +#define _H_TOKEN + +#include "securityserver.h" +#include "structure.h" +#include "pcsc++.h" + + +class Reader; + + +// +// Token is the global-scope object representing a smartcard token +// +class Token : public PerGlobal { +public: + Token(); + ~Token(); + + Reader &reader() const; + + void insert(Reader &slot); + void remove(); + + void notify(NotificationEvent event); + + IFDUMP(void dumpNode()); + +private: + PCSC::ReaderState mState; +}; + + +#endif //_H_TOKEN diff --git a/src/tokendatabase.cpp b/src/tokendatabase.cpp new file mode 100644 index 0000000..653898b --- /dev/null +++ b/src/tokendatabase.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// tokendatabase - software database container implementation. +// +#include "tokendatabase.h" +#include "process.h" +#include "key.h" + + +class TokenKey : public Key { +public: +}; + + +// +// Construct a TokenDatabase +// +TokenDatabase::TokenDatabase(Process &proc) + : Database(proc) +{ + proc.addReference(*this); +} + + +// +// Basic Database virtual implementations +// +TokenDbCommon &TokenDatabase::common() const +{ + return parent(); +} + +const char *TokenDatabase::dbName() const +{ + return "<>"; +} + + +static inline TokenKey &myKey(Key &key) +{ + return safer_cast(key); +} + + + + +// +// Key inquiries +// +CSSM_KEY_SIZE TokenDatabase::queryKeySize(Key &key) +{ + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + + +// +// Signatures and MACs +// +void TokenDatabase::generateSignature(const Context &context, Key &key, + CSSM_ALGORITHMS signOnlyAlgorithm, const CssmData &data, CssmData &signature) +{ + key.validate(CSSM_ACL_AUTHORIZATION_SIGN, context); + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + +void TokenDatabase::verifySignature(const Context &context, Key &key, + CSSM_ALGORITHMS verifyOnlyAlgorithm, const CssmData &data, const CssmData &signature) +{ + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + +void TokenDatabase::generateMac(const Context &context, Key &key, + const CssmData &data, CssmData &mac) +{ + key.validate(CSSM_ACL_AUTHORIZATION_MAC, context); + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + +void TokenDatabase::verifyMac(const Context &context, Key &key, + const CssmData &data, const CssmData &mac) +{ + key.validate(CSSM_ACL_AUTHORIZATION_MAC, context); + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + + +// +// Encryption/decryption +// +void TokenDatabase::encrypt(const Context &context, Key &key, + const CssmData &clear, CssmData &cipher) +{ + key.validate(CSSM_ACL_AUTHORIZATION_ENCRYPT, context); + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + +void TokenDatabase::decrypt(const Context &context, Key &key, + const CssmData &cipher, CssmData &clear) +{ + key.validate(CSSM_ACL_AUTHORIZATION_DECRYPT, context); + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + + +// +// Key generation and derivation. +// Currently, we consider symmetric key generation to be fast, but +// asymmetric key generation to be (potentially) slow. +// +void TokenDatabase::generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, RefPointer &newKey) +{ + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + +void TokenDatabase::generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 pubUsage, uint32 pubAttrs, uint32 privUsage, uint32 privAttrs, + RefPointer &publicKey, RefPointer &privateKey) +{ + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + +RefPointer TokenDatabase::deriveKey(const Context &context, Key *baseKey, + const AccessCredentials *cred, const AclEntryPrototype *owner, + CssmData *param, uint32 usage, uint32 attrs) +{ + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + + +// +// Key wrapping and unwrapping. +// Note that the key argument (the key in the context) is optional because of the special +// case of "cleartext" (null algorithm) wrapping for import/export. +// + +void TokenDatabase::wrapKey(const Context &context, Key *key, + Key &keyToBeWrapped, const AccessCredentials *cred, + const CssmData &descriptiveData, CssmKey &wrappedKey) +{ + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + +RefPointer TokenDatabase::unwrapKey(const Context &context, Key *key, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, const CssmKey wrappedKey, + Key *publicKey, CssmData *descriptiveData) +{ + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + + +// +// Miscellaneous CSSM functions +// +uint32 TokenDatabase::getOutputSize(const Context &context, Key &key, uint32 inputSize, bool encrypt) +{ + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} + + +// +// (Re-)Authenticate the database. This changes the stored credentials. +// +void TokenDatabase::authenticate(const AccessCredentials *cred) +{ + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); +} diff --git a/src/tokendatabase.h b/src/tokendatabase.h new file mode 100644 index 0000000..6f8ccba --- /dev/null +++ b/src/tokendatabase.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// tokendatabase - software database container implementation. +// +// A TokenDatabase is a software storage container, +// implemented in cooperation by the AppleCSLDP CDSA plugin and this daemon. +// +#ifndef _H_TOKENDATABASE +#define _H_TOKENDATABASE + +#include "database.h" + +class TokenDatabase; +class TokenDbCommon; +class TokenKey; + + +// +// TokenDatabase DbCommons +// +class TokenDbCommon : public DbCommon { +public: + TokenDbCommon(Session &ssn); +public: + +}; + + +// +// A Database object represents an Apple CSP/DL open database (DL/DB) object. +// It maintains its protected semantic state (including keys) and provides controlled +// access. +// +class TokenDatabase : public Database { + friend class TokenDbCommon; +public: + TokenDatabase(Process &proc); + + TokenDbCommon &common() const; + + const char *dbName() const; + +public: + CSSM_KEY_SIZE queryKeySize(Key &key); + + // service calls + void generateSignature(const Context &context, Key &key, CSSM_ALGORITHMS signOnlyAlgorithm, + const CssmData &data, CssmData &signature); + void verifySignature(const Context &context, Key &key, CSSM_ALGORITHMS verifyOnlyAlgorithm, + const CssmData &data, const CssmData &signature); + void generateMac(const Context &context, Key &key, + const CssmData &data, CssmData &mac); + void verifyMac(const Context &context, Key &key, + const CssmData &data, const CssmData &mac); + + void encrypt(const Context &context, Key &key, const CssmData &clear, CssmData &cipher); + void decrypt(const Context &context, Key &key, const CssmData &cipher, CssmData &clear); + + void generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, RefPointer &newKey); + void generateKey(const Context &context, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 pubUsage, uint32 pubAttrs, uint32 privUsage, uint32 privAttrs, + RefPointer &publicKey, RefPointer &privateKey); + RefPointer deriveKey(const Context &context, Key *key, + const AccessCredentials *cred, const AclEntryPrototype *owner, + CssmData *param, uint32 usage, uint32 attrs); + + void wrapKey(const Context &context, Key *key, + Key &keyToBeWrapped, const AccessCredentials *cred, + const CssmData &descriptiveData, CssmKey &wrappedKey); + RefPointer unwrapKey(const Context &context, Key *key, + const AccessCredentials *cred, const AclEntryPrototype *owner, + uint32 usage, uint32 attrs, const CssmKey wrappedKey, + Key *publicKey, CssmData *descriptiveData); + + uint32 getOutputSize(const Context &context, Key &key, uint32 inputSize, bool encrypt = true); + +public: + // encoding/decoding databases + void authenticate(const AccessCredentials *cred); +}; + + +#endif //_H_TOKENDATABASE diff --git a/src/transition.cpp b/src/transition.cpp new file mode 100644 index 0000000..0d1f909 --- /dev/null +++ b/src/transition.cpp @@ -0,0 +1,1107 @@ +/* + * Copyright (c) 2000-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// transition - SecurityServer IPC-to-class-methods transition layer +// +#include +#include "server.h" +#include "session.h" +#include "database.h" +#include "kcdatabase.h" +#include "kckey.h" +#include "transwalkers.h" +#include + +#include +#include + +// +// Bracket Macros +// +#define UCSP_ARGS mach_port_t servicePort, mach_port_t replyPort, security_token_t securityToken, \ + CSSM_RETURN *rcode +#define CONTEXT_ARGS Context context, Pointer contextBase, Context::Attr *attributes, mach_msg_type_number_t attrSize + +#define BEGIN_IPCN *rcode = CSSM_OK; try { +#define BEGIN_IPC BEGIN_IPCN RefPointer connRef(&Server::connection(replyPort)); \ +Connection &connection __attribute__((unused)) = *connRef; +#define END_IPC(base) END_IPCN(base) Server::requestComplete(); return KERN_SUCCESS; +#define END_IPCN(base) } \ + catch (const CommonError &err) { *rcode = CssmError::cssmError(err, CSSM_ ## base ## _BASE_ERROR); } \ + catch (const std::bad_alloc &) { *rcode = CssmError::merge(CSSM_ERRCODE_MEMORY_ERROR, CSSM_ ## base ## _BASE_ERROR); } \ + catch (Connection *conn) { *rcode = 0; } \ + catch (...) { *rcode = CssmError::merge(CSSM_ERRCODE_INTERNAL_ERROR, CSSM_ ## base ## _BASE_ERROR); } + +#define DATA_IN(base) void *base, mach_msg_type_number_t base##Length +#define DATA_OUT(base) void **base, mach_msg_type_number_t *base##Length +#define DATA(base) CssmData(base, base##Length) + +#define COPY_IN(type,name) type *name, mach_msg_type_number_t name##Length, type *name##Base +#define COPY_OUT(type,name) \ + type **name, mach_msg_type_number_t *name##Length, type **name##Base + + +using LowLevelMemoryUtilities::increment; +using LowLevelMemoryUtilities::difference; + + +// +// An OutputData object will take memory allocated within the SecurityServer, +// hand it to the MIG return-output parameters, and schedule it to be released +// after the MIG reply has been sent. It will also get rid of it in case of +// error. +// +class OutputData : public CssmData { +public: + OutputData(void **outP, mach_msg_type_number_t *outLength) + : mData(*outP), mLength(*outLength) { } + ~OutputData() + { mData = data(); mLength = length(); Server::releaseWhenDone(mData); } + + void operator = (const CssmData &source) + { CssmData::operator = (source); } + +private: + void * &mData; + mach_msg_type_number_t &mLength; +}; + + +// +// Setup/Teardown functions. +// +kern_return_t ucsp_server_setup(UCSP_ARGS, mach_port_t taskPort, ClientSetupInfo info, const char *identity) +{ + BEGIN_IPCN + Server::active().setupConnection(Server::connectNewProcess, servicePort, replyPort, + taskPort, securityToken, &info, identity); + END_IPCN(CSSM) + return KERN_SUCCESS; +} + +kern_return_t ucsp_server_setupNew(UCSP_ARGS, mach_port_t taskPort, + ClientSetupInfo info, const char *identity, + mach_port_t *newServicePort) +{ + BEGIN_IPCN + try { + RefPointer session = new DynamicSession(TaskPort(taskPort).bootstrap()); + Server::active().setupConnection(Server::connectNewSession, session->servicePort(), replyPort, + taskPort, securityToken, &info, identity); + *newServicePort = session->servicePort(); + } catch (const MachPlusPlus::Error &err) { + switch (err.error) { + case BOOTSTRAP_SERVICE_ACTIVE: + MacOSError::throwMe(errSessionAuthorizationDenied); // translate + default: + throw; + } + } + END_IPCN(CSSM) + return KERN_SUCCESS; +} + +kern_return_t ucsp_server_setupThread(UCSP_ARGS, mach_port_t taskPort) +{ + BEGIN_IPCN + Server::active().setupConnection(Server::connectNewThread, servicePort, replyPort, + taskPort, securityToken); + END_IPCN(CSSM) + return KERN_SUCCESS; +} + + +kern_return_t ucsp_server_teardown(UCSP_ARGS) +{ + BEGIN_IPCN + Server::active().endConnection(replyPort); + END_IPCN(CSSM) + return KERN_SUCCESS; +} + + + +// +// DL Interface +// +kern_return_t ucsp_server_attach(UCSP_ARGS, COPY_IN(CssmSubserviceUid, ssUid), AttachmentHandle *attachment) +{ + BEGIN_IPC + secdebug("dl", "attach"); + relocate(ssUid, ssUidBase, ssUidLength); + // @@@ + *attachment = 0; + END_IPC(DL) +} + +kern_return_t ucsp_server_detach(UCSP_ARGS, AttachmentHandle attachment) +{ + BEGIN_IPC + secdebug("dl", "detach"); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_openDb(UCSP_ARGS, AttachmentHandle attachment, DATA_IN(name), + COPY_IN(CssmNetAddress, location), CSSM_DB_ACCESS_TYPE accessType, + COPY_IN(AccessCredentials, accessCredentials), DATA_IN(openParameters), DbHandle *db) +{ + BEGIN_IPC + secdebug("dl", "openDb"); + //DATA(name); + relocate(location, locationBase, locationLength); + relocate(accessCredentials, accessCredentialsBase, accessCredentialsLength); + //DATA(openParameters); + // @@@ + *db = 0; + END_IPC(DL) +} + +kern_return_t ucsp_server_createDb2(UCSP_ARGS, AttachmentHandle attachment, DATA_IN(name), + COPY_IN(CssmNetAddress, location), COPY_IN(CSSM_DBINFO, dbInfo), CSSM_DB_ACCESS_TYPE accessType, + COPY_IN(AccessCredentials, accessCredentials), COPY_IN(AclEntryPrototype, aclEntryPrototype), + DATA_IN(openParameters), DbHandle *db) +{ + BEGIN_IPC + secdebug("dl", "createDb2"); + //DATA(name); + relocate(location, locationBase, locationLength); + relocate(dbInfo, dbInfoBase, dbInfoLength); + relocate(accessCredentials, accessCredentialsBase, accessCredentialsLength); + relocate(aclEntryPrototype, aclEntryPrototypeBase, aclEntryPrototypeLength); + //DATA(openParameters); + // @@@ + *db = 0; + END_IPC(DL) +} + +kern_return_t ucsp_server_deleteDb(UCSP_ARGS, AttachmentHandle attachment, + DATA_IN(name), COPY_IN(CssmNetAddress, location), COPY_IN(AccessCredentials, accessCredentials)) +{ + BEGIN_IPC + secdebug("dl", "deleteDb"); + //DATA(name); + relocate(location, locationBase, locationLength); + relocate(accessCredentials, accessCredentialsBase, accessCredentialsLength); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_getDbNames(UCSP_ARGS, AttachmentHandle attachment, + COPY_OUT(CSSM_NAME_LIST, outNameList)) +{ + BEGIN_IPC + secdebug("dl", "getDbNames"); + // @@@ + + CSSM_NAME_LIST *nameList; + //Attachment &a = Proccess::attachment(attachment); + //nameList = a.copyNameList(); + Copier nameLists(nameList, Allocator::standard()); // make flat copy + //a.freeNameList(nameList); // Release original + *outNameListLength = nameLists.length(); + //flips(nameLists.value(), outNameList, outNameListBase); + Server::releaseWhenDone(nameLists.keep()); // throw flat copy out when done + END_IPC(DL) +} + +kern_return_t ucsp_server_getDbNameFromHandle(UCSP_ARGS, DbHandle db, DATA_OUT(name)) +{ + BEGIN_IPC + secdebug("dl", "getDbNameFromHandle"); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_closeDb(UCSP_ARGS, DbHandle db) +{ + BEGIN_IPC + secdebug("dl", "closeDb"); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_authenticateDb(UCSP_ARGS, DbHandle db, + CSSM_DB_ACCESS_TYPE accessType, COPY_IN(AccessCredentials, cred)) +{ + BEGIN_IPC + secdebug("dl", "authenticateDb"); + relocate(cred, credBase, credLength); + // @@@ Pass in accessType. + Server::database(db)->authenticate(cred); + END_IPC(DL) +} + +kern_return_t ucsp_server_createRelation(UCSP_ARGS, DbHandle db, + CSSM_DB_RECORDTYPE recordType, + RelationName relationName, + uint32 attributeCount, + COPY_IN(CSSM_DB_SCHEMA_ATTRIBUTE_INFO, attributes), + uint32 indexCount, + COPY_IN(CSSM_DB_SCHEMA_INDEX_INFO, indices)) +{ + BEGIN_IPC + secdebug("dl", "createRelation"); + CheckingReconstituteWalker relocator(attributes, attributesBase, attributesLength, + Server::process().byteFlipped()); + for (uint32 ix = 0; ix < attributeCount; ++ix) + walk(relocator, attributes[ix]); + CheckingReconstituteWalker relocator2(indices, indicesBase, indicesLength, + Server::process().byteFlipped()); + for (uint32 ix = 0; ix < indexCount; ++ix) + walk(relocator, indices[ix]); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_destroyRelation(UCSP_ARGS, DbHandle db, CSSM_DB_RECORDTYPE recordType) +{ + BEGIN_IPC + secdebug("dl", "destroyRelation"); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_insertRecord(UCSP_ARGS, DbHandle db, CSSM_DB_RECORDTYPE recordType, + COPY_IN(CssmDbRecordAttributeData, attributes), DATA_IN(data), RecordHandle *record) +{ + BEGIN_IPC + secdebug("dl", "insertRecord"); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_deleteRecord(UCSP_ARGS, RecordHandle record) +{ + BEGIN_IPC + secdebug("dl", "deleteRecord"); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_modifyRecord(UCSP_ARGS, RecordHandle record, CSSM_DB_RECORDTYPE recordType, + COPY_IN(CssmDbRecordAttributeData, attributes), DATA_IN(data), CSSM_DB_MODIFY_MODE modifyMode) +{ + BEGIN_IPC + secdebug("dl", "modifyRecord"); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_findFirstRecord(UCSP_ARGS, DbHandle db, + COPY_IN(CssmQuery, query), + SearchHandle *search, + COPY_IN(CssmDbRecordAttributeData, inAttributes), + COPY_OUT(CssmDbRecordAttributeData, outAttributes), + boolean_t getData, + DATA_OUT(data), + RecordHandle *record +) +{ + BEGIN_IPC + secdebug("dl", "findFirstRecord"); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_findNextRecord(UCSP_ARGS, SearchHandle search, + COPY_IN(CssmDbRecordAttributeData, inAttributes), + COPY_OUT(CssmDbRecordAttributeData, outAttributes), + boolean_t getData, + DATA_OUT(data), + RecordHandle *record) +{ + BEGIN_IPC + secdebug("dl", "findNextRecord"); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_abortFind(UCSP_ARGS, SearchHandle search) +{ + BEGIN_IPC + secdebug("dl", "abortFind"); + //delete &Process::search(search); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_getRecordFromHandle(UCSP_ARGS, RecordHandle record, + COPY_IN(CssmDbRecordAttributeData, inAttributes), + COPY_OUT(CssmDbRecordAttributeData, outAttributes), + boolean_t getData, + DATA_OUT(data)) +{ + BEGIN_IPC + secdebug("dl", "getRecordFromHandle"); + relocate(inAttributes, inAttributesBase, inAttributesLength); + // @@@ + END_IPC(DL) +} + +kern_return_t ucsp_server_freeRecordHandle(UCSP_ARGS, RecordHandle record) +{ + BEGIN_IPC + secdebug("dl", "freeRecordHandle"); + //delete &Process::record(record); + // @@@ + END_IPC(DL) +} + + +// +// Database management +// +kern_return_t ucsp_server_createDb(UCSP_ARGS, DbHandle *db, + COPY_IN(DLDbFlatIdentifier, ident), + COPY_IN(AccessCredentials, cred), COPY_IN(AclEntryPrototype, owner), + DBParameters params) +{ + BEGIN_IPC + relocate(cred, credBase, credLength); + relocate(owner, ownerBase, ownerLength); + relocate(ident, identBase, identLength); + *db = (new KeychainDatabase(*ident, params, connection.process(), cred, owner))->handle(); + END_IPC(DL) +} + +kern_return_t ucsp_server_decodeDb(UCSP_ARGS, DbHandle *db, + COPY_IN(DLDbFlatIdentifier, ident), COPY_IN(AccessCredentials, cred), DATA_IN(blob)) +{ + BEGIN_IPC + relocate(cred, credBase, credLength); + relocate(ident, identBase, identLength); + *db = (new KeychainDatabase(*ident, DATA(blob).interpretedAs(), + connection.process(), cred))->handle(); + END_IPC(DL) +} + +kern_return_t ucsp_server_encodeDb(UCSP_ARGS, DbHandle db, DATA_OUT(blob)) +{ + BEGIN_IPC + DbBlob *dbBlob = Server::keychain(db)->blob(); // memory owned by database + *blob = dbBlob; + *blobLength = dbBlob->length(); + END_IPC(DL) +} + +kern_return_t ucsp_server_releaseDb(UCSP_ARGS, DbHandle db) +{ + BEGIN_IPC + connection.process().removeReference(*Server::database(db)); + END_IPC(DL) +} + +kern_return_t ucsp_server_getDbIndex(UCSP_ARGS, DbHandle db, DATA_OUT(index)) +{ + BEGIN_IPC + OutputData indexData(index, indexLength); + Server::keychain(db)->getDbIndex(indexData); + END_IPC(DL) +} + +kern_return_t ucsp_server_authenticateDb(UCSP_ARGS, DbHandle db, + COPY_IN(AccessCredentials, cred)) +{ + BEGIN_IPC + relocate(cred, credBase, credLength); + Server::database(db)->authenticate(cred); + END_IPC(DL) +} + +kern_return_t ucsp_server_setDbParameters(UCSP_ARGS, DbHandle db, DBParameters params) +{ + BEGIN_IPC + Server::keychain(db)->setParameters(params); + END_IPC(DL) +} + +kern_return_t ucsp_server_getDbParameters(UCSP_ARGS, DbHandle db, DBParameters *params) +{ + BEGIN_IPC + Server::keychain(db)->getParameters(*params); + END_IPC(DL) +} + +kern_return_t ucsp_server_changePassphrase(UCSP_ARGS, DbHandle db, + COPY_IN(AccessCredentials, cred)) +{ + BEGIN_IPC + relocate(cred, credBase, credLength); + Server::keychain(db)->changePassphrase(cred); + END_IPC(DL) +} + +kern_return_t ucsp_server_lockDb(UCSP_ARGS, DbHandle db) +{ + BEGIN_IPC + Server::keychain(db)->lockDb(); + END_IPC(DL) +} + +kern_return_t ucsp_server_lockAll (UCSP_ARGS, boolean_t forSleep) +{ + BEGIN_IPC + connection.session().allReferences(&DbCommon::sleepProcessing); + END_IPC(DL) +} + +kern_return_t ucsp_server_unlockDb(UCSP_ARGS, DbHandle db) +{ + BEGIN_IPC + Server::keychain(db)->unlockDb(); + END_IPC(DL) +} + +kern_return_t ucsp_server_unlockDbWithPassphrase(UCSP_ARGS, DbHandle db, DATA_IN(passphrase)) +{ + BEGIN_IPC + Server::keychain(db)->unlockDb(DATA(passphrase)); + END_IPC(DL) +} + +kern_return_t ucsp_server_isLocked(UCSP_ARGS, DbHandle db, boolean_t *locked) +{ + BEGIN_IPC + *locked = Server::keychain(db)->isLocked(); + END_IPC(DL) +} + + +// +// Key management +// +kern_return_t ucsp_server_encodeKey(UCSP_ARGS, KeyHandle keyh, DATA_OUT(blob), + boolean_t wantUid, DATA_OUT(uid)) +{ + BEGIN_IPC + RefPointer gKey = Server::key(keyh); + if (KeychainKey *key = dynamic_cast(gKey.get())) { + KeyBlob *keyBlob = key->blob(); // still owned by key + *blob = keyBlob; + *blobLength = keyBlob->length(); + if (wantUid) { // uid generation is not implemented + CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); + } else { + *uidLength = 0; // do not return this + } + } else { // not a KeychainKey + CssmError::throwMe(CSSMERR_CSP_INVALID_KEY_REFERENCE); + } + END_IPC(CSP) +} + +kern_return_t ucsp_server_decodeKey(UCSP_ARGS, KeyHandle *keyh, CssmKey::Header *header, + DbHandle db, DATA_IN(blob)) +{ + BEGIN_IPC + RefPointer key = new KeychainKey(*Server::keychain(db), DATA(blob).interpretedAs()); + key->returnKey(*keyh, *header); + flip(*header); + END_IPC(CSP) +} + +kern_return_t ucsp_server_releaseKey(UCSP_ARGS, KeyHandle keyh) +{ + BEGIN_IPC + RefPointer key = Server::key(keyh); + key->database().releaseKey(*key); + END_IPC(CSP) +} + +kern_return_t ucsp_server_queryKeySizeInBits(UCSP_ARGS, KeyHandle keyh, CSSM_KEY_SIZE *length) +{ + BEGIN_IPC + RefPointer key = Server::key(keyh); + *length = key->database().queryKeySize(*key); + END_IPC(CSP) +} + +kern_return_t ucsp_server_getOutputSize(UCSP_ARGS, CONTEXT_ARGS, KeyHandle keyh, + uint32 inputSize, boolean_t encrypt, uint32 *outputSize) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + RefPointer key = Server::key(keyh); + *outputSize = key->database().getOutputSize(context, *key, inputSize, encrypt); + END_IPC(CSP) +} + +kern_return_t ucsp_server_getKeyDigest(UCSP_ARGS, KeyHandle key, DATA_OUT(digest)) +{ + BEGIN_IPC + CssmData digestData = Server::key(key)->canonicalDigest(); + *digest = digestData.data(); + *digestLength = digestData.length(); + END_IPC(CSP) +} + +// +// RNG interface +// +kern_return_t ucsp_server_generateRandom(UCSP_ARGS, uint32 bytes, DATA_OUT(data)) +{ + BEGIN_IPC + Allocator &allocator = Allocator::standard(Allocator::sensitive); + void *buffer = allocator.malloc(bytes); + Server::active().random(buffer, bytes); + *data = buffer; + *dataLength = bytes; + Server::releaseWhenDone(allocator, buffer); + END_IPC(CSP) +} + + +// +// Signatures and MACs +// +kern_return_t ucsp_server_generateSignature(UCSP_ARGS, CONTEXT_ARGS, KeyHandle keyh, + CSSM_ALGORITHMS signOnlyAlgorithm, DATA_IN(data), DATA_OUT(signature)) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + RefPointer key = Server::key(keyh); + OutputData sigData(signature, signatureLength); + key->database().generateSignature(context, *key, signOnlyAlgorithm, + DATA(data), sigData); + END_IPC(CSP) +} + +kern_return_t ucsp_server_verifySignature(UCSP_ARGS, CONTEXT_ARGS, KeyHandle keyh, + CSSM_ALGORITHMS verifyOnlyAlgorithm, DATA_IN(data), DATA_IN(signature)) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + RefPointer key = Server::key(keyh); + key->database().verifySignature(context, *key, verifyOnlyAlgorithm, + DATA(data), DATA(signature)); + END_IPC(CSP) +} + +kern_return_t ucsp_server_generateMac(UCSP_ARGS, CONTEXT_ARGS, KeyHandle keyh, + DATA_IN(data), DATA_OUT(mac)) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + RefPointer key = Server::key(keyh); + OutputData macData(mac, macLength); + key->database().generateMac(context, *key, DATA(data), macData); + END_IPC(CSP) +} + +kern_return_t ucsp_server_verifyMac(UCSP_ARGS, CONTEXT_ARGS, KeyHandle keyh, + DATA_IN(data), DATA_IN(mac)) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + RefPointer key = Server::key(keyh); + key->database().verifyMac(context, *key, DATA(data), DATA(mac)); + END_IPC(CSP) +} + + +// +// Encryption/Decryption +// +kern_return_t ucsp_server_encrypt(UCSP_ARGS, CONTEXT_ARGS, KeyHandle keyh, + DATA_IN(clear), DATA_OUT(cipher)) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + RefPointer key = Server::key(keyh); + OutputData cipherOut(cipher, cipherLength); + key->database().encrypt(context, *key, DATA(clear), cipherOut); + END_IPC(CSP) +} + +kern_return_t ucsp_server_decrypt(UCSP_ARGS, CONTEXT_ARGS, KeyHandle keyh, + DATA_IN(cipher), DATA_OUT(clear)) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + RefPointer key = Server::key(keyh); + OutputData clearOut(clear, clearLength); + key->database().decrypt(context, *key, DATA(cipher), clearOut); + END_IPC(CSP) +} + + +// +// Key generation +// +kern_return_t ucsp_server_generateKey(UCSP_ARGS, DbHandle db, CONTEXT_ARGS, + COPY_IN(AccessCredentials, cred), COPY_IN(AclEntryPrototype, owner), + uint32 usage, uint32 attrs, KeyHandle *newKey, CssmKey::Header *newHeader) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + relocate(cred, credBase, credLength); + relocate(owner, ownerBase, ownerLength); + //@@@ preliminary interpretation - will get "type handle" + RefPointer database = Server::optionalDatabase(db); + RefPointer key; + database->generateKey(context, cred, owner, usage, attrs, key); + key->returnKey(*newKey, *newHeader); + flip(*newHeader); + END_IPC(CSP) +} + +kern_return_t ucsp_server_generateKeyPair(UCSP_ARGS, DbHandle db, CONTEXT_ARGS, + COPY_IN(AccessCredentials, cred), COPY_IN(AclEntryPrototype, owner), + uint32 pubUsage, uint32 pubAttrs, uint32 privUsage, uint32 privAttrs, + KeyHandle *pubKey, CssmKey::Header *pubHeader, KeyHandle *privKey, CssmKey::Header *privHeader) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + relocate(cred, credBase, credLength); + relocate(owner, ownerBase, ownerLength); + RefPointer pub, priv; + RefPointer database = Server::optionalDatabase(db); + database->generateKey(context, cred, owner, + pubUsage, pubAttrs, privUsage, privAttrs, pub, priv); + pub->returnKey(*pubKey, *pubHeader); + flip(*pubHeader); + priv->returnKey(*privKey, *privHeader); + flip(*privHeader); + END_IPC(CSP) +} + + +// +// Key derivation. +// This is a bit strained; the incoming 'param' value may have structure +// and needs to be handled on a per-algorithm basis, which means we have to +// know which key derivation algorithms we support for passing to our CSP(s). +// The default behavior is to handle "flat" data blobs, which is as good +// a default as we can manage. +// NOTE: The param-specific handling must be synchronized with the client library +// code (in sstransit.h). +// +kern_return_t ucsp_server_deriveKey(UCSP_ARGS, DbHandle db, CONTEXT_ARGS, KeyHandle keyh, + COPY_IN(AccessCredentials, cred), COPY_IN(AclEntryPrototype, owner), + COPY_IN(void, paramInputData), DATA_OUT(paramOutput), + uint32 usage, uint32 attrs, KeyHandle *newKey, CssmKey::Header *newHeader) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + relocate(cred, credBase, credLength); + relocate(owner, ownerBase, ownerLength); + + // munge together the incoming 'param' value according to algorithm + CssmData param; + switch (context.algorithm()) { + case CSSM_ALGID_PKCS5_PBKDF2: + relocate((CSSM_PKCS5_PBKDF2_PARAMS *)paramInputData, + (CSSM_PKCS5_PBKDF2_PARAMS *)paramInputDataBase, + paramInputDataLength); + param = CssmData(paramInputData, sizeof(CSSM_PKCS5_PBKDF2_PARAMS)); + break; + default: + param = CssmData(paramInputData, paramInputDataLength); + break; + } + RefPointer database = Server::optionalDatabase(db); + RefPointer theKey = database->deriveKey(context, Server::optionalKey(keyh), + cred, owner, ¶m, usage, attrs); + theKey->returnKey(*newKey, *newHeader); + flip(*newHeader); + if (param.length()) { + if (!param) // CSP screwed up + CssmError::throwMe(CSSM_ERRCODE_INTERNAL_ERROR); + if (paramInputDataLength) // using incoming buffer; make a copy + param = CssmAutoData(Server::csp().allocator(), param).release(); + OutputData(paramOutput, paramOutputLength) = param; // return the data + } + END_IPC(CSP) +} + + +// +// Key wrapping and unwrapping +// +kern_return_t ucsp_server_wrapKey(UCSP_ARGS, CONTEXT_ARGS, KeyHandle keyh, + COPY_IN(AccessCredentials, cred), KeyHandle keyToBeWrappedh, + DATA_IN(descriptiveData), CssmKey *wrappedKey, DATA_OUT(keyData)) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + relocate(cred, credBase, credLength); + RefPointer inputKey = Server::key(keyToBeWrappedh); + inputKey->database().wrapKey(context, Server::optionalKey(keyh), + *inputKey, cred, DATA(descriptiveData), *wrappedKey); + // transmit key data back as a separate blob + *keyData = wrappedKey->data(); + *keyDataLength = wrappedKey->length(); + Server::releaseWhenDone(*keyData); + flip(*wrappedKey); + END_IPC(CSP) +} + +kern_return_t ucsp_server_unwrapKey(UCSP_ARGS, DbHandle db, CONTEXT_ARGS, KeyHandle keyh, + COPY_IN(AccessCredentials, cred), COPY_IN(AclEntryPrototype, owner), + KeyHandle publicKeyh, CssmKey wrappedKey, DATA_IN(wrappedKeyData), + uint32 usage, uint32 attr, DATA_OUT(descriptiveData), + KeyHandle *newKey, CssmKey::Header *newHeader) +{ + BEGIN_IPC + relocate(context, contextBase, attributes, attrSize); + flip(wrappedKey); + wrappedKey.KeyData = DATA(wrappedKeyData); + relocate(cred, credBase, credLength); + relocate(owner, ownerBase, ownerLength); + RefPointer database = Server::optionalDatabase(db); + CssmData descriptiveDatas; + RefPointer theKey = database->unwrapKey(context, Server::optionalKey(keyh), + cred, owner, usage, attr, wrappedKey, + Server::optionalKey(publicKeyh), &descriptiveDatas); + theKey->returnKey(*newKey, *newHeader); + flip(*newHeader); + *descriptiveData = descriptiveDatas.data(); + *descriptiveDataLength = descriptiveDatas.length(); + Server::releaseWhenDone(*descriptiveData); + END_IPC(CSP) +} + + +// +// ACL management. +// Watch out for the memory-management tap-dance. +// +kern_return_t ucsp_server_getOwner(UCSP_ARGS, AclKind kind, KeyHandle key, + COPY_OUT(AclOwnerPrototype, ownerOut)) +{ + BEGIN_IPC + AclOwnerPrototype owner; + Server::aclBearer(kind, key).cssmGetOwner(owner); // allocates memory in owner + Copier owners(&owner, Allocator::standard()); // make flat copy + { ChunkFreeWalker free; walk(free, owner); } // release chunked original + *ownerOutLength = owners.length(); + flips(owners.value(), ownerOut, ownerOutBase); + Server::releaseWhenDone(owners.keep()); // throw flat copy out when done + END_IPC(CSP) +} + +kern_return_t ucsp_server_setOwner(UCSP_ARGS, AclKind kind, KeyHandle key, + COPY_IN(AccessCredentials, cred), COPY_IN(AclOwnerPrototype, owner)) +{ + BEGIN_IPC + relocate(cred, credBase, credLength); + relocate(owner, ownerBase, ownerLength); + Server::aclBearer(kind, key).cssmChangeOwner(*owner, cred); + END_IPC(CSP) +} + +kern_return_t ucsp_server_getAcl(UCSP_ARGS, AclKind kind, KeyHandle key, + boolean_t haveTag, const char *tag, + uint32 *countp, COPY_OUT(AclEntryInfo, acls)) +{ + BEGIN_IPC + uint32 count; + AclEntryInfo *aclList; + Server::aclBearer(kind, key).cssmGetAcl(haveTag ? tag : NULL, count, aclList); + *countp = count; + Copier aclsOut(AclEntryInfo::overlay(aclList), count); // make flat copy + + { // release the chunked memory originals + ChunkFreeWalker free; + for (uint32 n = 0; n < count; n++) + walk(free, aclList[n]); + + // release the memory allocated for the list itself when we are done + Allocator::standard().free (aclList); + } + + // set result (note: this is *almost* flips(), but on an array) + *aclsLength = aclsOut.length(); + *acls = *aclsBase = aclsOut; + if (flipClient()) { + FlipWalker w; + for (uint32 n = 0; n < count; n++) + walk(w, (*acls)[n]); + w.doFlips(); + Flippers::flip(*aclsBase); + } + Server::releaseWhenDone(aclsOut.keep()); + END_IPC(CSP) +} + +kern_return_t ucsp_server_changeAcl(UCSP_ARGS, AclKind kind, KeyHandle key, + COPY_IN(AccessCredentials, cred), CSSM_ACL_EDIT_MODE mode, CSSM_ACL_HANDLE handle, + COPY_IN(AclEntryInput, acl)) +{ + BEGIN_IPC + relocate(cred, credBase, credLength); + relocate(acl, aclBase, aclLength); + Server::aclBearer(kind, key).cssmChangeAcl(AclEdit(mode, handle, acl), cred); + END_IPC(CSP) +} + + +// +// Database key management. +// ExtractMasterKey looks vaguely like a key derivation operation, and is in fact +// presented by the CSPDL's CSSM layer as such. +// +kern_return_t ucsp_server_extractMasterKey(UCSP_ARGS, DbHandle db, CONTEXT_ARGS, DbHandle sourceDb, + COPY_IN(AccessCredentials, cred), COPY_IN(AclEntryPrototype, owner), + uint32 usage, uint32 attrs, KeyHandle *newKey, CssmKey::Header *newHeader) +{ + BEGIN_IPC + context.postIPC(contextBase, attributes); + relocate(cred, credBase, credLength); + relocate(owner, ownerBase, ownerLength); + RefPointer keychain = Server::keychain(sourceDb); + RefPointer masterKey = keychain->extractMasterKey(*Server::optionalDatabase(db), + cred, owner, usage, attrs); + masterKey->returnKey(*newKey, *newHeader); + flip(*newHeader); + END_IPC(CSP) +} + + +// +// Authorization subsystem support +// +kern_return_t ucsp_server_authorizationCreate(UCSP_ARGS, + COPY_IN(AuthorizationItemSet, inRights), + uint32 flags, + COPY_IN(AuthorizationItemSet, inEnvironment), + AuthorizationBlob *authorization) +{ + BEGIN_IPC + relocate(inRights, inRightsBase, inRightsLength); + relocate(inEnvironment, inEnvironmentBase, inEnvironmentLength); + Authorization::AuthItemSet rights(inRights), environment(inEnvironment); + + *rcode = connection.process().session().authCreate(rights, environment, + flags, *authorization, securityToken); + END_IPC(CSSM) +} + +kern_return_t ucsp_server_authorizationRelease(UCSP_ARGS, + AuthorizationBlob authorization, uint32 flags) +{ + BEGIN_IPC + connection.process().session().authFree(authorization, flags); + END_IPC(CSSM) +} + +kern_return_t ucsp_server_authorizationCopyRights(UCSP_ARGS, + AuthorizationBlob authorization, + COPY_IN(AuthorizationItemSet, inRights), + uint32 flags, + COPY_IN(AuthorizationItemSet, inEnvironment), + COPY_OUT(AuthorizationItemSet, result)) +{ + BEGIN_IPC + relocate(inRights, inRightsBase, inRightsLength); + relocate(inEnvironment, inEnvironmentBase, inEnvironmentLength); + Authorization::AuthItemSet rights(inRights), environment(inEnvironment), grantedRights; + *rcode = connection.process().session().authGetRights(authorization, + rights, environment, flags, grantedRights); + if (result && resultLength) + { + size_t resultSize; + grantedRights.copy(*result, resultSize); + *resultLength = resultSize; + *resultBase = *result; + flips(*result, result, resultBase); + Server::releaseWhenDone(*result); + } + END_IPC(CSSM) +} + +kern_return_t ucsp_server_authorizationCopyInfo(UCSP_ARGS, + AuthorizationBlob authorization, + AuthorizationString tag, + COPY_OUT(AuthorizationItemSet, info)) +{ + BEGIN_IPC + Authorization::AuthItemSet infoSet; + *info = *infoBase = NULL; + *infoLength = 0; + *rcode = connection.process().session().authGetInfo(authorization, + tag[0] ? tag : NULL, infoSet); + if (*rcode == noErr) { + size_t infoSize; + infoSet.copy(*info, infoSize); + *infoLength = infoSize; + *infoBase = *info; + flips(*info, info, infoBase); + Server::releaseWhenDone(*info); + } + END_IPC(CSSM) +} + +kern_return_t ucsp_server_authorizationExternalize(UCSP_ARGS, + AuthorizationBlob authorization, AuthorizationExternalForm *extForm) +{ + BEGIN_IPC + *rcode = connection.process().session().authExternalize(authorization, *extForm); + END_IPC(CSSM) +} + +kern_return_t ucsp_server_authorizationInternalize(UCSP_ARGS, + AuthorizationExternalForm extForm, AuthorizationBlob *authorization) +{ + BEGIN_IPC + *rcode = connection.process().session().authInternalize(extForm, *authorization); + END_IPC(CSSM) +} + + +// +// Session management subsystem +// +kern_return_t ucsp_server_getSessionInfo(UCSP_ARGS, + SecuritySessionId *sessionId, SessionAttributeBits *attrs) +{ + BEGIN_IPC + Session &session = Session::find(*sessionId); + *sessionId = session.handle(); + *attrs = session.attributes(); + END_IPC(CSSM) +} + +kern_return_t ucsp_server_setupSession(UCSP_ARGS, + SessionCreationFlags flags, SessionAttributeBits attrs) +{ + BEGIN_IPC + Session::setup(flags, attrs); + END_IPC(CSSM) +} + + +// +// Notification core subsystem +// +kern_return_t ucsp_server_requestNotification(UCSP_ARGS, mach_port_t receiver, uint32 domain, uint32 events) +{ + BEGIN_IPC + connection.process().requestNotifications(receiver, domain, events); + END_IPC(CSSM) +} + +kern_return_t ucsp_server_stopNotification(UCSP_ARGS, mach_port_t receiver) +{ + BEGIN_IPC + connection.process().stopNotifications(receiver); + END_IPC(CSSM) +} + +kern_return_t ucsp_server_postNotification(UCSP_ARGS, uint32 domain, uint32 event, DATA_IN(data)) +{ + BEGIN_IPC + Listener::notify(domain, event, DATA(data)); + END_IPC(CSSM) +} + + +// +// AuthorizationDB modification +// +kern_return_t ucsp_server_authorizationdbGet(UCSP_ARGS, const char *rightname, DATA_OUT(rightDefinition)) +{ + BEGIN_IPC + CFDictionaryRef rightDict; + + *rcode = connection.process().session().authorizationdbGet(rightname, &rightDict); + + if (!*rcode && rightDict) + { + CFRef data(CFPropertyListCreateXMLData (NULL, rightDict)); + CFRelease(rightDict); + if (!data) + return errAuthorizationInternal; + + // @@@ copy data to avoid having to do a delayed cfrelease + mach_msg_type_number_t length = CFDataGetLength(data); + void *xmlData = Allocator::standard().malloc(length); + memcpy(xmlData, CFDataGetBytePtr(data), length); + Server::releaseWhenDone(xmlData); + + *rightDefinition = xmlData; + *rightDefinitionLength = length; + } + END_IPC(CSSM) +} + +kern_return_t ucsp_server_authorizationdbSet(UCSP_ARGS, AuthorizationBlob authorization, const char *rightname, DATA_IN(rightDefinition)) +{ + BEGIN_IPC + CFRef data(CFDataCreate(NULL, (UInt8 *)rightDefinition, rightDefinitionLength)); + + if (!data) + return errAuthorizationInternal; + + CFRef rightDefinition(static_cast(CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable, NULL))); + + if (!rightDefinition || (CFGetTypeID(rightDefinition) != CFDictionaryGetTypeID())) + return errAuthorizationInternal; + + *rcode = connection.process().session().authorizationdbSet(authorization, rightname, rightDefinition); + + END_IPC(CSSM) +} + +kern_return_t ucsp_server_authorizationdbRemove(UCSP_ARGS, AuthorizationBlob authorization, const char *rightname) +{ + BEGIN_IPC + *rcode = connection.process().session().authorizationdbRemove(authorization, rightname); + END_IPC(CSSM) +} + + +// +// Miscellaneous administrative functions +// +kern_return_t ucsp_server_addCodeEquivalence(UCSP_ARGS, DATA_IN(oldHash), DATA_IN(newHash), + const char *name, boolean_t forSystem) +{ + BEGIN_IPC + Server::codeSignatures().addLink(DATA(oldHash), DATA(newHash), name, forSystem); + END_IPC(CSSM) +} + +kern_return_t ucsp_server_removeCodeEquivalence(UCSP_ARGS, DATA_IN(hash), + const char *name, boolean_t forSystem) +{ + BEGIN_IPC + Server::codeSignatures().removeLink(DATA(hash), name, forSystem); + END_IPC(CSSM) +} + +kern_return_t ucsp_server_setAlternateSystemRoot(UCSP_ARGS, const char *root) +{ + BEGIN_IPC +#if defined(NDEBUG) + if (connection.process().uid() != 0) + CssmError::throwMe(CSSM_ERRCODE_OS_ACCESS_DENIED); +#endif //NDEBUG + Server::codeSignatures().open((string(root) + EQUIVALENCEDBPATH).c_str()); + END_IPC(CSSM) +} diff --git a/src/transwalkers.cpp b/src/transwalkers.cpp new file mode 100644 index 0000000..50a54db --- /dev/null +++ b/src/transwalkers.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// transwalkers - server side transition data walking support +// +// These are data walker operators for securely marshaling and unmarshaling +// data structures across IPC. They are also in charge of fixing byte order +// inconsistencies between server and clients. +// +#include + + +using LowLevelMemoryUtilities::increment; +using LowLevelMemoryUtilities::difference; + + +bool flipClient() +{ + return Server::process().byteFlipped(); +} + + +// +// CheckingRelocateWalkers +// +CheckingReconstituteWalker::CheckingReconstituteWalker(void *ptr, void *base, size_t size, bool flip) + : mBase(base), mFlip(flip) +{ + if (mFlip) + Flippers::flip(mBase); // came in reversed; fix for base use + mOffset = difference(ptr, mBase); + mLimit = increment(mBase, size); +} + + +// +// Relocation support +// +void relocate(Context &context, void *base, Context::Attr *attrs, uint32 attrSize) +{ + flip(context); + CheckingReconstituteWalker relocator(attrs, base, attrSize, flipClient()); + context.ContextAttributes = attrs; // fix context->attr vector link + for (uint32 n = 0; n < context.attributesInUse(); n++) + walk(relocator, context[n]); +} + + +// +// Outbound flipping support +// +FlipWalker::~FlipWalker() +{ + for (set::const_iterator it = mFlips.begin(); it != mFlips.end(); it++) + delete it->impl; +} + +void FlipWalker::doFlips(bool active) +{ + if (active) { + secdebug("flipwalkers", "starting outbound flips"); + for (set::const_iterator it = mFlips.begin(); it != mFlips.end(); it++) + it->impl->flip(); + secdebug("flipwalkers", "outbound flips done"); + } +} diff --git a/src/transwalkers.h b/src/transwalkers.h new file mode 100644 index 0000000..67b4cb6 --- /dev/null +++ b/src/transwalkers.h @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// transwalkers - server side transition data walking support +// +// These are data walker operators for securely marshaling and unmarshaling +// data structures across IPC. They are also in charge of fixing byte order +// inconsistencies between server and clients. +// +#ifndef _H_TRANSWALKERS +#define _H_TRANSWALKERS + +#include +#include "flippers.h" +#include "server.h" +#include + +using LowLevelMemoryUtilities::increment; +using LowLevelMemoryUtilities::difference; + + +// +// Should we flip data? +// This looks at the current client's process information (a thread-global state) +// to determine flip status. Valid (only) within BEGIN_IPC/END_IPC brackets. +// +bool flipClient(); + + +// +// A CheckingReconstituteWalker is a variant of an ordinary ReconstituteWalker +// that checks object pointers and sizes against the incoming block limits. +// It throws an exception if incoming data has pointers outside the incoming block. +// This avoids trouble inside of the SecurityServer caused (by bug or malice) +// from someone spoofing the client access side. +// +class CheckingReconstituteWalker { +private: + void check(void *addr, size_t size) + { + if (addr < mBase || increment(addr, size) > mLimit) + CssmError::throwMe(CSSM_ERRCODE_INVALID_POINTER); + } + +public: + CheckingReconstituteWalker(void *ptr, void *base, size_t size, bool flip); + + template + void operator () (T &obj, size_t size = sizeof(T)) + { + check(increment(&obj, -mOffset), size); + if (mFlip) + Flippers::flip(obj); + } + + template + void operator () (T * &addr, size_t size = sizeof(T)) + { + DEBUGWALK("checkreconst:ptr"); + if (addr) { + // process the pointer + void *p = addr; + blob(p, size); + addr = reinterpret_cast(p); + + // now flip the contents + if (mFlip) + Flippers::flip(*addr); + } + } + + template + void blob(T * &addr, size_t size) + { + DEBUGWALK("checkreconst:blob"); + if (addr) { + // flip the address (the pointer itself) + if (mFlip) { + secdebug("flippers", "flipping %s@%p", Debug::typeName(addr).c_str(), addr); + Flippers::flip(addr); + } + + // check the address against the transmitted bounds + check(addr, size); + + // relocate it + addr = increment(addr, mOffset); + } + } + + static const bool needsRelinking = true; + static const bool needsSize = false; + +private: + void *mBase; // old base address + void *mLimit; // old last byte address + 1 + off_t mOffset; // relocation offset + bool mFlip; // apply byte order flipping +}; + + +// +// Process an incoming (IPC) data blob of type T. +// This relocates pointers to fit in the local address space, +// and fixes byte order issues as needed. +// +template +void relocate(T *obj, T *base, size_t size) +{ + if (obj) { + if (base == NULL) // invalid, could confuse walkers + CssmError::throwMe(CSSM_ERRCODE_INVALID_POINTER); + CheckingReconstituteWalker relocator(obj, base, size, + Server::process().byteFlipped()); + walk(relocator, base); + } +} + + +// +// Special handling for incoming CSSM contexts. +// +void relocate(Context &context, void *base, Context::Attr *attrs, uint32 attrSize); + + +// +// A FlipWalker is a walker operator that collects its direct invocations +// into a set of memory objects. These objects can then collectively be +// byte-flipped (exactly once :-) at the flick of a function. +// +class FlipWalker { +private: + struct FlipBase { + virtual ~FlipBase() { } + virtual void flip() const = 0; + }; + + template + struct FlipRef : public FlipBase { + T &obj; + FlipRef(T &s) : obj(s) { } + void flip() const +{ secdebug("outflip", "%p flip/ref %s@%p", this, Debug::typeName(obj).c_str(), &obj); +{ Flippers::flip(obj); } +} + }; + + template + struct FlipPtr : public FlipBase { + T * &obj; + FlipPtr(T * &s) : obj(s) { } + void flip() const +{ secdebug("outflip", "%p flip/ptr %s@%p(%p)", this, Debug::typeName(obj).c_str(), &obj, obj); +{ Flippers::flip(*obj); Flippers::flip(obj); } +} +}; + + template + struct FlipBlob : public FlipBase { + T * &obj; + FlipBlob(T * &s) : obj(s) { } + void flip() const +{ secdebug("outflip", "%p flip/blob %s@%p(%p)", this, Debug::typeName(obj).c_str(), &obj, obj); +{ Flippers::flip(obj); } +} + }; + + struct Flipper { + FlipBase *impl; + Flipper(FlipBase *p) : impl(p) { } + bool operator < (const Flipper &other) const + { return impl < other.impl; } + }; + +public: + ~FlipWalker(); + void doFlips(bool active = true); + + template + void operator () (T &obj, size_t = sizeof(T)) + { mFlips.insert(new FlipRef(obj)); } + + template + T *operator () (T * &addr, size_t size = sizeof(T)) + { mFlips.insert(new FlipPtr(addr)); return addr; } + + template + void blob(T * &addr, size_t size) + { mFlips.insert(new FlipBlob(addr)); } + + static const bool needsRelinking = true; + static const bool needsSize = true; + +private: + set mFlips; +}; + + +// +// A raw flip, conditioned on the client's flip state +// +template +void flip(T &addr) +{ + if (flipClient()) { + secdebug("flippers", "raw flipping %s", Debug::typeName(addr).c_str()); + Flippers::flip(addr); + } +} + +template +void flips(T *value, T ** &addr, T ** &base) +{ + *addr = *base = value; + if (flipClient()) { + FlipWalker w; // collector + walk(w, value); // collect all flippings needed + w.doFlips(); // execute flips (flips value but leaves addr alone) + Flippers::flip(*base); // flip base (so it arrives right side up) + } +} + + +#endif //_H_TRANSWALKERS diff --git a/src/yarrowMigTypes.h b/src/yarrowMigTypes.h new file mode 100644 index 0000000..cd8871c --- /dev/null +++ b/src/yarrowMigTypes.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// yarrowMigTypes.h - type equivalence declarations for Yarrow's MIG +// interface +// +#include + +// @@@ who forgot that one? +extern "C" kern_return_t mig_deallocate(vm_address_t addr, vm_size_t size); + +namespace Security +{ + +typedef void *Data; + +// +// The server's bootstrap name +// +#define YARROW_SERVER_NAME "YarrowServer" + +} // end namespace Security + +using namespace Security; diff --git a/tests/AZNTest.cpp b/tests/AZNTest.cpp new file mode 100644 index 0000000..b4c1082 --- /dev/null +++ b/tests/AZNTest.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +/* + * AZNTest.cpp + * SecurityServer + * + * Created by michael on Fri Oct 20 2000. + */ + +#include + +#include + +using namespace Authorization; + +static const AuthorizationItem gItems[] = +{ + {"login", 0, NULL, NULL}, + {"reboot", 0, NULL, NULL}, + {"shutdown", 0, NULL, NULL}, + {"mount", 0, NULL, NULL}, + {"login.reboot", 0, NULL, NULL}, + {"login.shutdown", 0, NULL, NULL}, + {"unmount", 0, NULL, NULL} +}; + +static const AuthorizationRights gRights = +{ + 7, + const_cast(gItems) +}; + +void +printRights(const RightSet &rightSet) +{ + for(RightSet::const_iterator it = rightSet.begin(); it != rightSet.end(); ++it) + { + printf("right: \"%s\"\n", it->rightName()); + } +} + +int +main(int argc, char **argv) +{ + Engine engine("/tmp/config.plist"); + + const RightSet inputRights(&gRights); + MutableRightSet outputRights; + printf("InputRights:\n"); + printRights(inputRights); + printf("Authorizing:\n"); + OSStatus result = engine.authorize(inputRights, NULL, + kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights | kAuthorizationFlagPartialRights, + NULL, NULL, &outputRights); + printf("Result: %ld\n", result); + printf("OutputRights:\n"); + printRights(outputRights); + return 0; +} diff --git a/tests/auth.plist b/tests/auth.plist new file mode 100644 index 0000000..b9732dc --- /dev/null +++ b/tests/auth.plist @@ -0,0 +1,19 @@ + + + + + debug. + + group + yes + shared + + timeout + 300 + + debug.allow + allow + debug.deny + deny + + diff --git a/tests/exectest.cpp b/tests/exectest.cpp new file mode 100644 index 0000000..8809f29 --- /dev/null +++ b/tests/exectest.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// Exectest - privileged-execution test driver +// +#include +#include +#include + + +void doLoopback(int argc, char *argv[]); + + +int main(int argc, char **argv) +{ + const char *path = "/usr/bin/id"; + bool writeToPipe = false; + bool loopback = false; + + int arg; + extern char *optarg; + extern int optind; + while ((arg = getopt(argc, argv, "f:lLw")) != -1) { + switch (arg) { + case 'f': + path = optarg; + break; + case 'l': + loopback = true; + break; + case 'L': + doLoopback(argc, argv); + exit(0); + case 'w': + writeToPipe = true; + break; + case '?': + exit(2); + } + } + + AuthorizationItem right = { "system.privilege.admin", 0, NULL, 0 }; + AuthorizationRights rights = { 1, &right }; + + AuthorizationRef auth; + if (OSStatus error = AuthorizationCreate(&rights, NULL /*env*/, + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagExtendRights | + kAuthorizationFlagPreAuthorize, + &auth)) { + printf("create error %ld\n", error); + exit(1); + } + + if (loopback) { + path = argv[0]; + argv[--optind] = "-L"; // backing over existing array element + } + + FILE *f; + if (OSStatus error = AuthorizationExecuteWithPrivileges(auth, + path, 0, argv + optind, &f)) { + printf("exec error %ld\n", error); + exit(1); + } + printf("--- execute successful ---\n"); + if (writeToPipe) { + char buffer[1024]; + while (fgets(buffer, sizeof(buffer), stdin)) + fprintf(f, "%s", buffer); + } else { + char buffer[1024]; + while (fgets(buffer, sizeof(buffer), f)) + printf("%s", buffer); + } + printf("--- end of output ---\n"); + exit(0); +} + + +void doLoopback(int argc, char *argv[]) +{ + // general status + printf("Authorization Execution Loopback Test\n"); + printf("Invoked as"); + for (int n = 0; argv[n]; n++) + printf(" %s", argv[n]); + printf("\n"); + + // recover the authorization handle + AuthorizationRef auth; + if (OSStatus err = AuthorizationCopyPrivilegedReference(&auth, 0)) { + printf("Cannot recover AuthorizationRef: error=%ld\n", err); + exit(1); + } + + printf("AuthorizationRef recovered.\n"); +} diff --git a/tests/testacls.cpp b/tests/testacls.cpp new file mode 100644 index 0000000..7975f41 --- /dev/null +++ b/tests/testacls.cpp @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// testacls - ACL-related test cases. +// +#include "testclient.h" +#include "testutils.h" +#include + +using namespace CodeSigning; + + +// +// ACL get/set tests +// +void acls() +{ + printf("* Basic ACL tests\n"); + CssmAllocator &alloc = CssmAllocator::standard(); + ClientSession ss(alloc, alloc); + + // create key with initial ACL + StringData initialAclPassphrase("very secret"); + AclEntryPrototype initialAcl; + initialAcl.TypedSubject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD, + new(alloc) ListElement(initialAclPassphrase)); + AclEntryInput initialAclInput(initialAcl); + AclTester tester(ss, &initialAclInput); + + // get the owner and verify + AclOwnerPrototype owner; + ss.getKeyOwner(tester.keyRef, owner); + assert(owner.subject().type() == CSSM_ACL_SUBJECT_TYPE_PASSWORD); + assert(owner.subject().length() == 1); + + // get the acl entry and verify + { + uint32 count; + AclEntryInfo *acls; + ss.getKeyAcl(tester.keyRef, NULL/*tag*/, count, acls); + assert(count == 1); + const AclEntryInfo &acl1 = acls[0]; + const TypedList &subject1 = acl1.proto().subject(); + assert(subject1.type() == CSSM_ACL_SUBJECT_TYPE_PASSWORD); + assert(subject1.length() == 1); + } + + // try to use the key and see... + tester.testWrap(&nullCred, "ACCEPTING NULL CREDENTIAL"); + AutoCredentials cred(alloc); + cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(StringData("wrongo"))); + tester.testWrap(&cred, "ACCEPTING WRONG PASSWORD CREDENTIAL"); + cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(StringData("very secret"))); + tester.testWrap(&cred); + + // now *replace* the ACL entry with a new one... + { + detail("Changing ACL"); + uint32 count; + AclEntryInfo *infos; + ss.getKeyAcl(tester.keyRef, NULL, count, infos); + assert(count == 1); // one entry + + AclEntryPrototype newAcl; + TypedList subject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_THRESHOLD, + new(alloc) ListElement(2), new(alloc) ListElement(3)); + subject += new(alloc) ListElement(TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "check me!"))); + subject += new(alloc) ListElement(TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "once again!"))); + subject += new(alloc) ListElement(TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "hug me!"))); + newAcl.TypedSubject = subject; + AclEntryInput input(newAcl); + AclEdit edit(infos[0].handle(), input); + + try { + AutoCredentials nullCred(alloc); + ss.changeKeyAcl(tester.keyRef, nullCred, edit); + error("ALLOWED ACL EDIT WITHOUT CREDENTIALS"); + } catch (CssmCommonError &err) { + detail(err, "Acl Edit rejected properly"); + } + ss.changeKeyAcl(tester.keyRef, cred, edit); + detail("ACL changed OK"); + } + + // ... and see how the new one reacts + tester.testWrap(&nullCred, "ACCEPTING NULL CREDENTIALS NOW"); + tester.testWrap(&cred, "ACCEPTING OLD CREDENTIALS FOR NEW ACL"); + { + AutoCredentials cred(alloc); + cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "check me!")); + tester.testWrap(&cred, "ACCEPTING LEAF SAMPLE WITHOUT THRESHOLD FRAMEWORK"); + } + + // Threshold subjects + { + detail("Testing threshold ACLs"); + AutoCredentials cred(alloc); + TypedList &threshold = cred += TypedList(alloc, CSSM_SAMPLE_TYPE_THRESHOLD, + new(alloc) ListElement(TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "wrongo!"))) + ); + tester.testWrap(&cred, "ACCEPTING ALL WRONG SAMPLES IN THRESHOLD"); + threshold += new(alloc) ListElement(TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "hug me!"))); + tester.testWrap(&cred, "ACCEPTING TOO FEW THRESHOLD SAMPLES"); + threshold += new(alloc) ListElement(TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "check me!"))); + tester.testWrap(&cred); + // stuff the ballot box + threshold += new(alloc) ListElement(TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "and this!"))); + threshold += new(alloc) ListElement(TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "and that!"))); + threshold += new(alloc) ListElement(TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(alloc, "and more!"))); +#ifdef STRICT_THRESHOLD_SUBJECTS + tester.testWrap(&cred, "ACCEPTING OVER-STUFFED THRESHOLD"); +#else + tester.testWrap(&cred); +#endif //STRICT_THRESHOLD_SUBJECTS + } + + // comment ACLs and tags + { + detail("Adding Comment entry"); + + AclEntryPrototype newAcl; + TypedList subject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_COMMENT, + new(alloc) ListElement(TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_THRESHOLD, + new(alloc) ListElement(alloc, "Robby Ray!"))), + new(alloc) ListElement(666)); + newAcl.TypedSubject = subject; + strcpy(newAcl.EntryTag, "vamos"); + AclEntryInput input(newAcl); + AclEdit edit(input); + ss.changeKeyAcl(tester.keyRef, cred, edit); + detail("Entry added"); + + uint32 count; + AclEntryInfo *infos; + ss.getKeyAcl(tester.keyRef, "vamos", count, infos); + assert(count == 1); // one entry (with this tag) + const AclEntryInfo &acl = infos[0]; + const TypedList &read = acl.proto().subject(); + assert(read.type() == CSSM_ACL_SUBJECT_TYPE_COMMENT); + assert(read.length() == 3); + assert(read[2] == 666); + CssmList &sublist = read[1]; + assert(sublist[0] == CSSM_ACL_SUBJECT_TYPE_THRESHOLD); + assert(string(sublist[1]) == "Robby Ray!"); + + detail("Comment entry retrieved okay"); + } +} + + +// +// ACL authorization tests +// +void authAcls() +{ + printf("* ACL authorizations test\n"); + CssmAllocator &alloc = CssmAllocator::standard(); + ClientSession ss(alloc, alloc); + + // create key with initial ACL + CSSM_ACL_AUTHORIZATION_TAG wrapTag = CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR; + CSSM_ACL_AUTHORIZATION_TAG encryptTag = CSSM_ACL_AUTHORIZATION_ENCRYPT; + StringData initialAclPassphrase("very secret"); + StringData the2ndAclPassword("most secret"); + AclEntryPrototype initialAcl; + initialAcl.TypedSubject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD, + new(alloc) ListElement(initialAclPassphrase)); + initialAcl.authorization().NumberOfAuthTags = 1; + initialAcl.authorization().AuthTags = &wrapTag; + AclEntryInput initialAclInput(initialAcl); + AclTester tester(ss, &initialAclInput); + + // get the owner and verify + AclOwnerPrototype owner; + ss.getKeyOwner(tester.keyRef, owner); + assert(owner.subject().type() == CSSM_ACL_SUBJECT_TYPE_PASSWORD); + assert(owner.subject().length() == 1); + + // get the acl entry and verify + { + uint32 count; + AclEntryInfo *acls; + ss.getKeyAcl(tester.keyRef, NULL/*tag*/, count, acls); + assert(count == 1); + const AclEntryInfo &acl1 = acls[0]; + const TypedList &subject1 = acl1.proto().subject(); + assert(subject1.type() == CSSM_ACL_SUBJECT_TYPE_PASSWORD); + assert(subject1.length() == 1); + const AuthorizationGroup &auths = acl1.proto().authorization(); + assert(auths.count() == 1); + assert(auths[0] == CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR); + } + + // try to use the key and see... + tester.testWrap(&nullCred, "ACCEPTING NULL CREDENTIAL"); + AutoCredentials cred(alloc); + cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(StringData("wrongo"))); + tester.testWrap(&cred, "ACCEPTING WRONG PASSWORD CREDENTIAL"); + cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(initialAclPassphrase)); + tester.testWrap(&cred); + + tester.testEncrypt(&nullCred, "ACCEPTING NULL CREDENTIAL FOR UNAUTHORIZED OPERATION"); + tester.testEncrypt(&cred, "ACCEPTING GOOD CREDENTIAL FOR UNAUTHORIZED OPERATION"); + + // now *add* a new ACL entry for encryption + { + detail("Adding new ACL entry"); + + AclEntryPrototype newAcl; + newAcl.TypedSubject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD, + new(alloc) ListElement(the2ndAclPassword)); + newAcl.authorization().NumberOfAuthTags = 1; + newAcl.authorization().AuthTags = &encryptTag; + AclEntryInput newInput(newAcl); + AclEdit edit(newInput); + + try { + AutoCredentials nullCred(alloc); + ss.changeKeyAcl(tester.keyRef, nullCred, edit); + error("ALLOWED ACL EDIT WITHOUT CREDENTIALS"); + } catch (CssmCommonError &err) { + detail(err, "Acl Edit rejected properly"); + } + ss.changeKeyAcl(tester.keyRef, cred, edit); + detail("ACL changed OK"); + + // read it back and check + { + uint32 count; + AclEntryInfo *acls; + ss.getKeyAcl(tester.keyRef, NULL/*tag*/, count, acls); + assert(count == 2); + const AclEntryInfo &acl1 = acls[0]; + const TypedList &subject1 = acl1.proto().subject(); + assert(subject1.type() == CSSM_ACL_SUBJECT_TYPE_PASSWORD); + assert(subject1.length() == 1); + const AuthorizationGroup &auths1 = acl1.proto().authorization(); + assert(auths1.count() == 1); + assert(auths1[0] == CSSM_ACL_AUTHORIZATION_EXPORT_CLEAR); + const AclEntryInfo &acl2 = acls[1]; + const TypedList &subject2 = acl2.proto().subject(); + assert(subject2.type() == CSSM_ACL_SUBJECT_TYPE_PASSWORD); + assert(subject2.length() == 1); + const AuthorizationGroup &auths2 = acl2.proto().authorization(); + assert(auths2.count() == 1); + assert(auths2[0] == CSSM_ACL_AUTHORIZATION_ENCRYPT); + } + } + + // ... and see how the new composite ACL behaves + AutoCredentials cred2(alloc); + cred2 += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(the2ndAclPassword)); + tester.testWrap(&nullCred, "ACCEPTING NULL CREDENTIALS FOR WRAPPING"); + tester.testEncrypt(&nullCred, "ACCEPTING NULL CREDENTIALS FOR ENCRYPTION"); + tester.testWrap(&cred); // "very secret" allows wrapping + tester.testEncrypt(&cred2); // "most secret" allows encrypting + tester.testWrap(&cred2, "ACCEPTING ENCRYPT CRED FOR WRAPPING"); + tester.testEncrypt(&cred, "ACCEPTING WRAP CRED FOR ENCRYPTING"); +} + + +// +// Keychain ACL subjects +// +void keychainAcls() +{ + printf("* Keychain (interactive) ACL test\n"); + CssmAllocator &alloc = CssmAllocator::standard(); + ClientSession ss(alloc, alloc); + + // create key with initial ACL + AclEntryPrototype initialAcl; + initialAcl.TypedSubject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT, + new(alloc) ListElement(alloc, "Test Key")); + AclEntryInput initialAclInput(initialAcl); + AclTester tester(ss, &initialAclInput); + + // get the owner and verify + AclOwnerPrototype owner; + ss.getKeyOwner(tester.keyRef, owner); + assert(owner.subject().type() == CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT); + assert(owner.subject().length() == 2); + + // get the acl entry and verify + { + uint32 count; + AclEntryInfo *acls; + ss.getKeyAcl(tester.keyRef, NULL/*tag*/, count, acls); + assert(count == 1); + const AclEntryInfo &acl1 = acls[0]; + const TypedList &subject1 = acl1.proto().subject(); + assert(subject1.type() == CSSM_ACL_SUBJECT_TYPE_KEYCHAIN_PROMPT); + assert(subject1.length() == 2); + assert(static_cast(subject1[1]) == "Test Key"); + } + + // try to use the key and see... + tester.testWrap(NULL, "ACCEPTING NULL CREDENTIAL"); + AutoCredentials cred(alloc); + cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(StringData("Test Key"))); + tester.testWrap(&cred, "ACCEPTING PASSWORD CREDENTIAL"); + cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT); + tester.testWrap(&cred); + // once again, for allow-this-pid feature testing + tester.testWrap(&cred); +} + + +// +// Code-signing ACL subjects +// +void codeSigning() +{ + printf("* Code Signing ACL test\n"); + CssmAllocator &alloc = CssmAllocator::standard(); + ClientSession ss(alloc, alloc); + + // sign ourselves + OSXSigner signer; + OSXCode *main = OSXCode::main(); + Signature *mySignature = signer.sign(*main); + detail("Code signature for testclient obtained"); + + // make a variant signature that isn't right + Signature *badSignature; + { + char buffer[512]; + assert(mySignature->length() <= sizeof(buffer)); + memcpy(buffer, mySignature->data(), mySignature->length()); + memcpy(buffer, "xyz!", 4); // 1 in 2^32 this is right... + badSignature = signer.restore(mySignature->type(), buffer, mySignature->length()); + } + + // create key with good code signature ACL + AclEntryPrototype initialAcl; + initialAcl.subject() = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_CODE_SIGNATURE, + new(alloc) ListElement(mySignature->type()), + new(alloc) ListElement(alloc.alloc(*mySignature))); + AclEntryInput initialAclInput(initialAcl); + AclTester tester(ss, &initialAclInput); + + // get the owner and verify + AclOwnerPrototype owner; + ss.getKeyOwner(tester.keyRef, owner); + assert(owner.subject().type() == CSSM_ACL_SUBJECT_TYPE_CODE_SIGNATURE); + assert(owner.subject().length() == 3); + + // we are us, so the SecurityServer should accept us + tester.testWrap(&nullCred); + + // now try this again with a *bad* signature... + AclEntryPrototype badAcl; + badAcl.TypedSubject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_CODE_SIGNATURE, + new(alloc) ListElement(badSignature->type()), + new(alloc) ListElement(alloc.alloc(*badSignature))); + AclEntryInput badAclInput(badAcl); + AclTester badTester(ss, &badAclInput); + badTester.testWrap(&nullCred, "BAD CODE SIGNATURE ACCEPTED"); + + // make sure the optional comment field makes it back out intact + // (reusing original initialAcl structures) + StringData comment("Walla Walla Washington!\nAbra cadabra.\n\n"); + initialAcl.subject() += new(alloc) ListElement(alloc, comment); + AclEntryInput initialAclInputWithComment(initialAcl); + AclTester commentTester(ss, &initialAclInputWithComment); + ss.getKeyOwner(commentTester.keyRef, owner); + assert(owner.subject().type() == CSSM_ACL_SUBJECT_TYPE_CODE_SIGNATURE); + assert(owner.subject().length() == 4); + assert(owner.subject()[3] == comment); + detail("Verified comment field intact"); +} diff --git a/tests/testauth.cpp b/tests/testauth.cpp new file mode 100644 index 0000000..d017a7c --- /dev/null +++ b/tests/testauth.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// testacls - ACL-related test cases. +// +#include "testclient.h" +#include "testutils.h" +#include + +using namespace CodeSigning; + + +// +// Authorization test. +// This tests the authorization API support. +// @@@ Incomplete and not satisfactory. +// +void authorizations() +{ + printf("* authorization test\n"); + ClientSession ss(CssmAllocator::standard(), CssmAllocator::standard()); + + // make a simple authorization query + AuthorizationBlob auth; + AuthorizationItem testingItem = { "debug.testing", 0, NULL, NULL }; + AuthorizationItem testingMoreItem = { "debug.testing.more", 0, NULL, NULL }; + AuthorizationItem denyItem = { "debug.deny", 0, NULL, NULL }; + AuthorizationItemSet request = { 1, &testingItem }; + ss.authCreate(&request, NULL/*environment*/, + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagExtendRights | + kAuthorizationFlagPartialRights, + auth); + detail("Initial authorization obtained"); + + // ask for rights from this authorization + { + AuthorizationItem moreItems[3] = { testingItem, denyItem, testingMoreItem }; + AuthorizationItemSet moreRequests = { 3, moreItems }; + AuthorizationItemSet *rightsVector; + ss.authCopyRights(auth, &moreRequests, NULL/*environment*/, + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagExtendRights | + kAuthorizationFlagPartialRights, + &rightsVector); + if (rightsVector->count != 2) + error("COPYRIGHTS RETURNED %d RIGHTS (EXPECTED 2)", int(rightsVector->count)); + // the output rights could be in either order -- be flexible + set rights; + rights.insert(rightsVector->items[0].name); + rights.insert(rightsVector->items[1].name); + assert(rights.find("debug.testing") != rights.end() && + rights.find("debug.testing.more") != rights.end()); + free(rightsVector); + detail("CopyRights okay"); + } + + // ask for the impossible + try { + AuthorizationBlob badAuth; + AuthorizationItem badItem = { "debug.deny", 0, NULL, NULL }; + AuthorizationItemSet badRequest = { 1, &badItem }; + ss.authCreate(&badRequest, NULL/*environment*/, + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagExtendRights, + auth); + error("AUTHORIZED debug.deny OPERATION"); + } catch (CssmCommonError &err) { + detail(err, "debug.deny authorization denied properly"); + } + + // externalize + AuthorizationExternalForm extForm; + ss.authExternalize(auth, extForm); + + // re-internalize + AuthorizationBlob auth2; + ss.authInternalize(extForm, auth2); + + // make sure it still works + { + AuthorizationItem moreItems[2] = { testingItem, denyItem }; + AuthorizationItemSet moreRequests = { 2, moreItems }; + AuthorizationItemSet *rightsVector; + ss.authCopyRights(auth2, &moreRequests, NULL/*environment*/, + kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagExtendRights | + kAuthorizationFlagPartialRights, + &rightsVector); + if (rightsVector->count != 1) + error("COPYRIGHTS RETURNED %d RIGHTS (EXPECTED 1)", int(rightsVector->count)); + assert(!strcmp(rightsVector->items[0].name, "debug.testing")); + free(rightsVector); + detail("Re-internalized authorization checks out okay"); + + // try it with no rights output (it's optional) + ss.authCopyRights(auth2, &moreRequests, NULL/*environment*/, + kAuthorizationFlagPartialRights, NULL); + detail("authCopyRights partial success OK (with no output)"); + + // but this will fail if we want ALL rights... + try { + ss.authCopyRights(auth2, &moreRequests, NULL/*environment*/, + kAuthorizationFlagDefaults, NULL); + error("authCopyRights succeeded with (only) partial success"); + } catch (CssmError &err) { + detail("authCopyRight failed for (only) partial success"); + } + } +} diff --git a/tests/testblobs.cpp b/tests/testblobs.cpp new file mode 100644 index 0000000..910d648 --- /dev/null +++ b/tests/testblobs.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// testacls - ACL-related test cases. +// +#include "testclient.h" +#include "testutils.h" + + +// +// Blob tests. +// Encodes and decodes Db and Key blobs and all that jazz. +// +void blobs() +{ + printf("* Database blob encryption test\n"); + ClientSession ss(CssmAllocator::standard(), CssmAllocator::standard()); + + DbTester db1(ss, "/tmp/one", NULL, 60, true); + DbTester db2(ss, "/tmp/two", NULL, 30, false); + + // encode db1, purge it, decode it again + CssmData dbBlob; + ss.encodeDb(db1, dbBlob); + DbHandle db1a = ss.decodeDb(db1.dbId, &nullCred, dbBlob); + ss.releaseDb(db1); + if (db1 == db1a) + detail("REUSED DB HANDLE ON DECODEDB (probably wrong)"); + DBParameters savedParams; + ss.getDbParameters(db1a, savedParams); + assert(savedParams.idleTimeout == db1.params.idleTimeout); + assert(savedParams.lockOnSleep == db1.params.lockOnSleep); + detail("Database encode/decode passed"); + + // make sure the old handle isn't valid anymore + try { + ss.getDbParameters(db1, savedParams); + printf("OLD DATABASE HANDLE NOT PURGED (possibly wrong)\n"); + } catch (const CssmCommonError &err) { + detail(err, "old DB handle rejected"); + } + + // open db1 a second time (so now there's two db handles for db1) + DbHandle db1b = ss.decodeDb(db1.dbId, &nullCred, dbBlob); + + // release both db1 handles and db2 + ss.releaseDb(db1a); + ss.releaseDb(db1b); + ss.releaseDb(db2); +} + + +// +// Database tests. +// Database locks/unlocks etc. +// +void databases() +{ + printf("* Database manipulation test\n"); + CssmAllocator &alloc = CssmAllocator::standard(); + ClientSession ss(alloc, alloc); + + AutoCredentials pwCred(alloc); + StringData passphrase("two"); + StringData badPassphrase("three"); + pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(passphrase)); + pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(badPassphrase)); + // pwCred = (NEW: two, OLD: three) + + DbTester db1(ss, "/tmp/one", NULL, 30, true); + DbTester db2(ss, "/tmp/two", &pwCred, 60, false); + // db2.passphrase = two + + // encode db1 and re-open it + CssmData dbBlob; + ss.encodeDb(db1, dbBlob); + DbHandle db1b = ss.decodeDb(db1.dbId, &nullCred, dbBlob); + if (db1b == db1.dbRef) + detail("REUSED DB HANDLE ON DECODEDB (probably wrong)"); + + // open db1 a third time (so now there's three db handles for db1) + DbHandle db1c = ss.decodeDb(db1.dbId, &nullCred, dbBlob); + + // lock them to get started + ss.lock(db1); + ss.lock(db2); + + // unlock it through user + prompt("unlock"); + ss.unlock(db1); + prompt(); + ss.unlock(db1b); // 2nd unlock should not prompt + ss.lock(db1c); // lock it again + prompt("unlock"); + ss.unlock(db1); // and that should prompt again + prompt(); + + // db2 has a passphrase lock credentials - it'll work without U/I + db2.unlock("wrong passphrase"); // pw=two, cred=three + AutoCredentials pwCred2(alloc); + pwCred2 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(passphrase)); + // pwCred2 = (OLD: two) + ss.authenticateDb(db2, CSSM_DB_ACCESS_WRITE, &pwCred2); // set it + db2.unlock(); + ss.lock(db2); + + // now change db2's passphrase + ss.lock(db2); + pwCred2 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(badPassphrase)); + // pwCred2 = (OLD: two, NEW: three) + db2.changePassphrase(&pwCred2); + // passphrase = three, cred = (OLD: two) + + // encode and re-decode to make sure new data is there + CssmData blob2; + ss.encodeDb(db2, blob2); + DbHandle db2a = ss.decodeDb(db2.dbId, &pwCred, blob2); + // db2a cred = (OLD: two, NEW: three) + + // now, the *old* cred won't work anymore + db2.unlock("old passphrase accepted"); + + // back to the old credentials, which *do* have the (old bad, now good) passphrase + ss.lock(db2a); + ss.unlock(db2a); + detail("New passphrase accepted"); + + // clear the credentials (this will prompt; cancel it) + ss.authenticateDb(db2, CSSM_DB_ACCESS_WRITE, NULL); + prompt("cancel"); + db2.unlock("null credential accepted"); + prompt(); + + // fell-swoop from-to change password operation + StringData newPassphrase("hollerith"); + AutoCredentials pwCred3(alloc); + pwCred3 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(newPassphrase)); + pwCred3 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(passphrase)); + db2.changePassphrase(&pwCred3, "accepting original (unchanged) passphrase"); + + AutoCredentials pwCred4(alloc); + pwCred4 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(newPassphrase)); + pwCred4 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(badPassphrase)); + db2.changePassphrase(&pwCred4); + + // final status check + AutoCredentials pwCred5(alloc); + pwCred5 += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(newPassphrase)); + ss.authenticateDb(db2, CSSM_DB_ACCESS_WRITE, &pwCred5); + db2.unlock(); + detail("Final passphrase change verified"); +} + + +// +// Key encryption tests. +// +void keyBlobs() +{ + printf("* Keyblob encryption test\n"); + CssmAllocator &alloc = CssmAllocator::standard(); + ClientSession ss(alloc, alloc); + + DLDbIdentifier dbId1(ssuid, "/tmp/one", NULL); + DBParameters initialParams1 = { 3600, false }; + + // create a new database + DbHandle db = ss.createDb(dbId1, NULL, NULL, initialParams1); + detail("Database created"); + + // establish an ACL for the key + StringData theAclPassword("Strenge Geheimsache"); + AclEntryPrototype initialAcl; + initialAcl.TypedSubject = TypedList(alloc, CSSM_ACL_SUBJECT_TYPE_PASSWORD, + new(alloc) ListElement(theAclPassword)); + AclEntryInput initialAclInput(initialAcl); + + AutoCredentials cred(alloc); + cred += TypedList(alloc, CSSM_SAMPLE_TYPE_PASSWORD, + new(alloc) ListElement(theAclPassword)); + + // generate a key + const CssmCryptoData seed(StringData("Farmers' day")); + FakeContext genContext(CSSM_ALGCLASS_KEYGEN, CSSM_ALGID_DES, + &::Context::Attr(CSSM_ATTRIBUTE_KEY_LENGTH, 64), + &::Context::Attr(CSSM_ATTRIBUTE_SEED, seed), + NULL); + KeyHandle key; + CssmKey::Header header; + ss.generateKey(db, genContext, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, + CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT, + /*cred*/NULL, &initialAclInput, key, header); + detail("Key generated"); + + // encrypt with the key + StringData clearText("Yet another boring cleartext sample string text sequence."); + StringData iv("Aardvark"); + CssmKey nullKey; memset(&nullKey, 0, sizeof(nullKey)); + FakeContext cryptoContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_DES, + &::Context::Attr(CSSM_ATTRIBUTE_KEY, nullKey), + &::Context::Attr(CSSM_ATTRIBUTE_INIT_VECTOR, iv), + &::Context::Attr(CSSM_ATTRIBUTE_MODE, CSSM_ALGMODE_CBC_IV8), + &::Context::Attr(CSSM_ATTRIBUTE_PADDING, CSSM_PADDING_PKCS1), + &::Context::Attr(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS, cred), + NULL); + CssmData cipherText; + ss.encrypt(cryptoContext, key, clearText, cipherText); + detail("Plaintext encrypted with original key"); + + // encode the key and release it + CssmData blob; + ss.encodeKey(key, blob); + ss.releaseKey(key); + detail("Key encoded and released"); + + // decode it again, re-introducing it + CssmKey::Header decodedHeader; + KeyHandle key2 = ss.decodeKey(db, blob, decodedHeader); + detail("Key decoded"); + + // decrypt with decoded key + CssmData recovered; + ss.decrypt(cryptoContext, key2, cipherText, recovered); + assert(recovered == clearText); + detail("Decoded key correctly decrypts ciphertext"); + + // check a few header fields + if (!memcmp(&header, &decodedHeader, sizeof(header))) { + detail("All header fields match"); + } else { + assert(header.algorithm() == decodedHeader.algorithm()); + assert(header.blobType() == decodedHeader.blobType()); + assert(header.blobFormat() == decodedHeader.blobFormat()); + assert(header.keyClass() == decodedHeader.keyClass()); + assert(header.attributes() == decodedHeader.attributes()); + assert(header.usage() == decodedHeader.usage()); + printf("Some header fields differ (probably okay)\n"); + } + + // make sure we need the credentials (destructive) + memset(&cred, 0, sizeof(cred)); + try { + ss.decrypt(cryptoContext, key2, cipherText, recovered); + error("RESTORED ACL FAILS TO RESTRICT"); + } catch (CssmError &err) { + detail(err, "Restored key restricts access properly"); + } +} diff --git a/tests/testclient.cpp b/tests/testclient.cpp new file mode 100644 index 0000000..05b72fd --- /dev/null +++ b/tests/testclient.cpp @@ -0,0 +1,289 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// Tester - test driver for securityserver client side. +// +#include "testclient.h" +#include "testutils.h" +#include // getopt(3) +#include + + +// +// Global constants +// +const CssmData null; // zero pointer, zero length constant data +const AccessCredentials nullCred; // null credentials + +CSSM_GUID ssguid = { 1,2,3 }; +CssmSubserviceUid ssuid(ssguid); + + +// +// Local functions +// +static void usage(); +static void runtest(char type); + + +// +// Default test set +// +static char testCodes[] = ".cesaAbdkKt"; + + +// +// Main program +// +int main(int argc, char *argv[]) +{ + setbuf(stdout, NULL); + + long ranseq = 0; // random stress test count + long ranseed = 1; // random seed for it + + int arg; + while ((arg = getopt(argc, argv, "r:v")) != -1) { + switch (arg) { + case 'r': { + ranseq = atoi(optarg); + if (const char *colon = strchr(optarg, ':')) + ranseed = atoi(colon + 1); + else + ranseed = getpid() ^ time(NULL); + break; + } + case 'v': + verbose = true; + break; + default: + usage(); + } + } + if (optind < argc - 1) + usage(); + const char *sequence = argv[optind]; + if (sequence && !strcmp(sequence, "+")) + sequence = testCodes; + + if (ranseq) { // repeated random (stress test) sequence + if (!sequence) + sequence = testCodes; + printf("*** Random stress test: %ld iterations from <%s> with seed=%ld\n", + ranseq, sequence, ranseed); + srandom(ranseed); + int setSize = strlen(sequence); + for (long n = 0; n < ranseq; n++) { + char type = sequence[random() % setSize]; + printf("\n[%ld:%c]", n, type); + runtest(type); + } + printf("*** Random test sequence complete.\n"); + exit(0); + } else { // single-pass selected tests sequence + if (!sequence) + sequence = "."; // default to ping test + for (const char *s = sequence; *s; s++) + runtest(*s); + printf("*** Test sequence complete.\n"); + exit(0); + } +} + +void usage() +{ + fprintf(stderr, "Usage: SSTester [-r count[:seed]] [-v] [%s|.|+]\n", + testCodes); + exit(2); +} + + +// +// Run a single type test +// +void runtest(char type) +{ + try { + debug("SStest", "Start test <%c>", type); + switch (type) { + case '.': // default + integrity(); + break; + case '-': + adhoc(); + break; + case 'a': + acls(); + break; + case 'A': + authAcls(); + break; + case 'b': + blobs(); + break; + case 'c': + codeSigning(); + break; + case 'd': + databases(); + break; + case 'e': + desEncryption(); + break; + case 'k': + keychainAcls(); + break; + case 'K': + keyBlobs(); + break; + case 's': + signWithRSA(); + break; + case 't': + authorizations(); + break; + case 'T': + timeouts(); + break; + default: + error("Invalid test selection (%c)", type); + } + printf("** Test step complete.\n"); + debug("SStest", "End test <%c>", type); + } catch (CssmCommonError &err) { + error(err, "Unexpected exception"); + } catch (...) { + error("Unexpected system exception"); + } +} + + +// +// Basic integrity test. +// +void integrity() +{ + ClientSession ss(CssmAllocator::standard(), CssmAllocator::standard()); + + printf("* Generating random sample: "); + DataBuffer<11> sample; + ss.generateRandom(sample); + for (uint32 n = 0; n < sample.length(); n++) + printf("%.2x", ((unsigned char *)sample)[n]); + printf("\n"); +} + + +// +// Database timeouts +// @@@ Incomplete and not satisfactory +// +void timeouts() +{ + printf("* Database timeout locks test\n"); + CssmAllocator &alloc = CssmAllocator::standard(); + ClientSession ss(alloc, alloc); + + DLDbIdentifier dbId1(ssuid, "/tmp/one", NULL); + DLDbIdentifier dbId2(ssuid, "/tmp/two", NULL); + DBParameters initialParams1 = { 4, false }; // 4 seconds timeout + DBParameters initialParams2 = { 8, false }; // 8 seconds timeout + + // credential to set keychain passphrase + AutoCredentials pwCred(alloc); + StringData password("mumbojumbo"); + pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(password)); + + DbHandle db1 = ss.createDb(dbId1, &pwCred, NULL, initialParams1); + DbHandle db2 = ss.createDb(dbId2, &pwCred, NULL, initialParams2); + detail("Databases created"); + + // generate a key + const CssmCryptoData seed(StringData("rain tonight")); + FakeContext genContext(CSSM_ALGCLASS_KEYGEN, CSSM_ALGID_DES, + &::Context::Attr(CSSM_ATTRIBUTE_KEY_LENGTH, 64), + &::Context::Attr(CSSM_ATTRIBUTE_SEED, seed), + NULL); + KeyHandle key; + CssmKey::Header header; + ss.generateKey(db1, genContext, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, + CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT, + /*cred*/NULL, NULL, key, header); + ss.generateKey(db2, genContext, CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, + CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT, + /*cred*/NULL, NULL, key, header); + detail("Keys generated and stored"); + + // credential to provide keychain passphrase + AutoCredentials pwCred2(alloc); + pwCred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, + new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), + new(alloc) ListElement(password)); + + //@@@ incomplete + ss.releaseDb(db1); + ss.releaseDb(db2); +} + + +// +// Ad-hoc test area. +// Used for whatever is needed at the moment... +// +void adhoc() +{ + printf("* Ad-hoc test sequence (now what does it do *this* time?)\n"); + + Cssm cssm1; + Cssm cssm2; + cssm1->init(); + cssm2->init(); + + { + Module m1(gGuidAppleCSP, cssm1); + Module m2(gGuidAppleCSP, cssm2); + CSP r1(m1); + CSP r2(m2); + + Digest d1(r1, CSSM_ALGID_SHA1); + Digest d2(r2, CSSM_ALGID_SHA1); + + StringData foo("foo de doo da blech"); + DataBuffer<30> digest1, digest2; + d1.digest(foo, digest1); + d2.digest(foo, digest2); + if (digest1 == digest2) + detail("Digests verify"); + else + error("Digests mismatch"); + } + + cssm1->terminate(); + cssm2->terminate(); +} diff --git a/tests/testclient.h b/tests/testclient.h new file mode 100644 index 0000000..c0b5e6f --- /dev/null +++ b/tests/testclient.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// Tester - test driver for securityserver client side. +// +#ifndef _H_TESTCLIENT +#define _H_TESTCLIENT + +#include "ssclient.h" +#include +#include +#include +#include +#include +#include + + +// +// Names from the SecurityServerSession class +// +using namespace SecurityServer; +using namespace CssmClient; + + +// +// Test drivers +// +void integrity(); +void signWithRSA(); +void desEncryption(); +void blobs(); +void keyBlobs(); +void databases(); +void timeouts(); +void acls(); +void authAcls(); +void codeSigning(); +void keychainAcls(); +void authorizations(); +void adhoc(); + + +// +// Global constants +// +extern const CssmData null; // zero pointer, zero length constant data +extern const AccessCredentials nullCred; // null credentials + +extern CSSM_GUID ssguid; // a fixed guid +extern CssmSubserviceUid ssuid; // a subservice-uid using this guid + + +#endif //_H_TESTCLIENT diff --git a/tests/testcrypto.cpp b/tests/testcrypto.cpp new file mode 100644 index 0000000..20329cb --- /dev/null +++ b/tests/testcrypto.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// Tester - test driver for securityserver client side. +// +#include "testclient.h" +#include "testutils.h" + + +// +// Simple run-through. +// This generates an RSA key, tests cleartext retrieval, signs a message, +// and veries it both ways. +// This is a basic integrity regression for the SecurityServer. +// +void signWithRSA() +{ + printf("* RSA key signing test\n"); + CSP csp(gGuidAppleCSP); + ClientSession ss(CssmAllocator::standard(), CssmAllocator::standard()); + StringData data("To sign or not to sign, is that the question?"); + + // set up dummy credentials + CssmKey dummyKey; memset(&dummyKey, 0, sizeof(dummyKey)); + CssmData nullData; + + // generate a key + detail("Asking for RSA key generation"); + KeyHandle publicKey, privateKey; + const CssmCryptoData seed(StringData("Seed ye well, my friend, and ye shall reap...")); + FakeContext genContext(CSSM_ALGCLASS_KEYGEN, CSSM_ALGID_RSA, + &::Context::Attr(CSSM_ATTRIBUTE_KEY_LENGTH, 512), + &::Context::Attr(CSSM_ATTRIBUTE_SEED, seed), + NULL); + CssmKey::Header pubHeader, privHeader; + ss.generateKey(noDb, genContext, + CSSM_KEYUSE_VERIFY, CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_RETURN_DATA, + CSSM_KEYUSE_SIGN, CSSM_KEYATTR_SENSITIVE, + NULL/*cred*/, NULL/*owner*/, publicKey, pubHeader, privateKey, privHeader); + detail("Key pair generated"); + + // retrieve the public key + CssmKey cpk; + FakeContext wrapContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_NONE, 0); + ss.wrapKey(wrapContext, noKey, publicKey, &nullCred, NULL, cpk); + Key clearPublicKey(csp, cpk); + detail("Retrieved public key"); + + // make sure we can't retrieve the private key + CssmKey clearPrivateKey; + try { + ss.wrapKey(wrapContext, noKey, privateKey, NULL/*cred*/, NULL, clearPrivateKey); + error("SecurityServer ACTUALLY gave us the PRIVATE key bits!"); + } catch (CssmError &err) { + detail(err, "Private key retrieval properly rejected"); + } + + // sign a message + CssmData signature; + FakeContext signContext(CSSM_ALGCLASS_SIGNATURE, CSSM_ALGID_SHA1WithRSA, + &::Context::Attr(CSSM_ATTRIBUTE_KEY, dummyKey), + NULL); + ss.generateSignature(signContext, privateKey, data, signature); + detail("Signature generated by SecurityServer"); + + // verify the signature (local) + { + Verify verifier(csp, CSSM_ALGID_SHA1WithRSA); + verifier.key(clearPublicKey); + verifier.verify(data, signature); + detail("Signature verified locally"); + } + + // verify the signature (SS) + ss.verifySignature(signContext, publicKey, data, signature); + detail("Signature verified by SecurityServer"); + + // falsify the signature (SS) + DataBuffer<200> falseData; + memcpy(falseData.data(), data.data(), data.length()); + falseData.length(data.length()); + ((char *)falseData)[3] = '?'; // alter message + try { + ss.verifySignature(signContext, publicKey, falseData, signature); + error("Altered message incorrectly verifies"); + } catch (CssmError &err) { + if (err.cssmError() == CSSMERR_CSP_VERIFY_FAILED) + detail("Verify of altered message successfully failed"); + else + error(err, "Unexpected exception on verify failure test"); + } +} + + +// +// Encrypt with DES +// +void desEncryption() +{ + printf("* DES encryption test\n"); + ClientSession ss(CssmAllocator::standard(), CssmAllocator::standard()); + CSP csp(gGuidAppleCSP); + + StringData clearText("Insert witty quotation here."); + StringData iv("abcdefgh"); + + // make up a DES key + StringData keyBits(strdup("Wallaby!")); + CssmKey keyForm(keyBits); + keyForm.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY; + keyForm.header().BlobType = CSSM_KEYBLOB_RAW; + keyForm.header().AlgorithmId = CSSM_ALGID_DES; + keyForm.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING; + Key key(csp, keyForm); + + // encrypt locally + DataBuffer<200> localCipher; + Encrypt localCrypt(csp, CSSM_ALGID_DES); + localCrypt.mode(CSSM_ALGMODE_CBC_IV8); + localCrypt.padding(CSSM_PADDING_PKCS1); + localCrypt.initVector(iv); + localCrypt.key(key); + CssmData remData; + size_t localLen = localCrypt.encrypt(clearText, localCipher, remData); + if (remData) + error("LOCAL ENCRYPTION OVERFLOWED"); + localCipher.length(localLen); + detail("Locally encrypted %ld bytes", localLen); + + // wrap in the key + CssmData unwrappedData; + ResourceControlContext owner; + FakeContext unwrapContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_NONE, 0); + KeyHandle keyRef; + CssmKey::Header keyHeader; + ss.unwrapKey(noDb, unwrapContext, noKey, noKey, + key, + CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, + CSSM_KEYATTR_RETURN_DEFAULT, + NULL/*cred*/, NULL/*owner*/, unwrappedData, keyRef, keyHeader); + detail("Placed key into SecurityServer; handle=%lx", keyRef); + + // encrypt remotely and compare + const CssmKey &tKey = key; + FakeContext cryptoContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_DES, + &::Context::Attr(CSSM_ATTRIBUTE_KEY, keyForm), + &::Context::Attr(CSSM_ATTRIBUTE_INIT_VECTOR, iv), + &::Context::Attr(CSSM_ATTRIBUTE_MODE, CSSM_ALGMODE_CBC_IV8), + &::Context::Attr(CSSM_ATTRIBUTE_PADDING, CSSM_PADDING_PKCS1), + NULL); + CssmData remoteCipher; + ss.encrypt(cryptoContext, keyRef, clearText, remoteCipher); + detail("Plaintext encrypted on SecurityServer"); + if (remoteCipher == localCipher) + detail("Ciphertexts verified"); + else + error("CIPHERTEXTS DIFFER"); + + // decrypt in SecurityServer + DataBuffer<200> clearRecovered; + ss.decrypt(cryptoContext, keyRef, localCipher, clearRecovered); + detail("Decrypted ciphertext in SecurityServer"); + if (clearRecovered == clearText) + detail("Plaintext recovered"); + else + error("PLAINTEXT MISMATCH"); +} + diff --git a/tests/testutils.cpp b/tests/testutils.cpp new file mode 100644 index 0000000..ebcbe18 --- /dev/null +++ b/tests/testutils.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// testutils - utilities for unit test drivers +// +#include "testutils.h" + +using namespace CssmClient; + +bool verbose = false; + + +// +// Error and diagnostic drivers +// +void error(const char *msg = NULL, ...) +{ + if (msg) { + va_list args; + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + putc('\n', stderr); + } + abort(); +} + +void error(const CssmCommonError &err, const char *msg = NULL, ...) +{ + if (msg) { + va_list args; + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + fprintf(stderr, ": %s", cssmErrorString(err.cssmError()).c_str()); + putc('\n', stderr); + } + abort(); +} + +void detail(const char *msg = NULL, ...) +{ + if (verbose) { + va_list args; + va_start(args, msg); + vfprintf(stdout, msg, args); + va_end(args); + putc('\n', stdout); + } +} + +void detail(const CssmCommonError &err, const char *msg) +{ + if (verbose) + printf("%s (ok): %s\n", msg, cssmErrorString(err).c_str()); +} + +void prompt(const char *msg) +{ + if (isatty(fileno(stdin))) + printf("[%s]", msg); +} + +void prompt() +{ + if (isatty(fileno(stdin))) + printf(" OK\n"); +} + + +// +// FakeContext management +// +FakeContext::FakeContext(CSSM_CONTEXT_TYPE type, CSSM_ALGORITHMS alg, uint32 count) +: Context(type, alg) +{ + NumberOfAttributes = count; + ContextAttributes = new Attr[count]; +} + + +FakeContext::FakeContext(CSSM_CONTEXT_TYPE type, CSSM_ALGORITHMS alg, ...) +: Context(type, alg) +{ + // count arguments + va_list args; + va_start(args, alg); + uint32 count = 0; + while (va_arg(args, Attr *)) + count++; + va_end(args); + + // make vector + NumberOfAttributes = count; + ContextAttributes = new Attr[count]; + + // stuff vector + va_start(args, alg); + for (uint32 n = 0; n < count; n++) + (*this)[n] = *va_arg(args, Attr *); + va_end(args); +} + + +// +// ACL test driver class +// +AclTester::AclTester(ClientSession &ss, const AclEntryInput *acl) : session(ss) +{ + // make up a DES key + StringData keyBits("Tweedle!"); + CssmKey key(keyBits); + key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY; + + // wrap in the key + CssmData unwrappedData; + FakeContext unwrapContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_NONE, 0); + CssmKey::Header keyHeader; + ss.unwrapKey(noDb, unwrapContext, noKey, noKey, + key, + CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT, + CSSM_KEYATTR_EXTRACTABLE, + NULL /*cred*/, acl, + unwrappedData, keyRef, keyHeader); + detail("Key seeded with ACL"); +} + + +void AclTester::testWrap(const AccessCredentials *cred, const char *howWrong) +{ + FakeContext wrapContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_NONE, 0); + CssmWrappedKey wrappedKey; + try { + session.wrapKey(wrapContext, noKey, keyRef, + cred, NULL /*descriptive*/, wrappedKey); + if (howWrong) { + error("WRAP MISTAKENLY SUCCEEDED: %s", howWrong); + } + detail("extract OK"); + } catch (const CssmCommonError &err) { + if (!howWrong) + error(err, "FAILED TO EXTRACT KEY"); + detail(err, "extract failed OK"); + } +} + +void AclTester::testEncrypt(const AccessCredentials *cred, const char *howWrong) +{ + CssmKey keyForm; memset(&keyForm, 0, sizeof(keyForm)); + StringData iv("Aardvark"); + StringData clearText("blah"); + CssmData remoteCipher; + try { + if (cred) { + FakeContext cryptoContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_DES, + &::Context::Attr(CSSM_ATTRIBUTE_KEY, keyForm), + &::Context::Attr(CSSM_ATTRIBUTE_INIT_VECTOR, iv), + &::Context::Attr(CSSM_ATTRIBUTE_MODE, CSSM_ALGMODE_CBC_IV8), + &::Context::Attr(CSSM_ATTRIBUTE_PADDING, CSSM_PADDING_PKCS1), + &::Context::Attr(CSSM_ATTRIBUTE_ACCESS_CREDENTIALS, *cred), + NULL); + session.encrypt(cryptoContext, keyRef, clearText, remoteCipher); + } else { + FakeContext cryptoContext(CSSM_ALGCLASS_SYMMETRIC, CSSM_ALGID_DES, + &::Context::Attr(CSSM_ATTRIBUTE_KEY, keyForm), + &::Context::Attr(CSSM_ATTRIBUTE_INIT_VECTOR, iv), + &::Context::Attr(CSSM_ATTRIBUTE_MODE, CSSM_ALGMODE_CBC_IV8), + &::Context::Attr(CSSM_ATTRIBUTE_PADDING, CSSM_PADDING_PKCS1), + NULL); + session.encrypt(cryptoContext, keyRef, clearText, remoteCipher); + } + if (howWrong) { + error("ENCRYPT MISTAKENLY SUCCEEDED: %s", howWrong); + } + detail("encrypt OK"); + } catch (CssmCommonError &err) { + if (!howWrong) + error(err, "FAILED TO ENCRYPT"); + detail(err, "encrypt failed"); + } +} + + +// +// Database test driver class +// +DbTester::DbTester(ClientSession &ss, const char *path, + const AccessCredentials *cred, int timeout, bool sleepLock) +: session(ss), dbId(ssuid, path, NULL) +{ + params.idleTimeout = timeout; + params.lockOnSleep = sleepLock; + dbRef = ss.createDb(dbId, cred, NULL, params); + detail("Database %s created", path); +} + + +void DbTester::unlock(const char *howWrong) +{ + session.lock(dbRef); + try { + session.unlock(dbRef); + if (howWrong) + error("DATABASE MISTAKENLY UNLOCKED: %s", howWrong); + } catch (CssmError &err) { + if (!howWrong) + error(err, howWrong); + detail(err, howWrong); + } +} + +void DbTester::changePassphrase(const AccessCredentials *cred, const char *howWrong) +{ + session.lock(dbRef); + try { + session.changePassphrase(dbRef, cred); + if (howWrong) + error("PASSPHRASE CHANGE MISTAKENLY SUCCEEDED: %s", howWrong); + } catch (CssmError &err) { + if (!howWrong) + error(err, howWrong); + detail(err, howWrong); + } +} diff --git a/tests/testutils.h b/tests/testutils.h new file mode 100644 index 0000000..d355064 --- /dev/null +++ b/tests/testutils.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2000-2001,2004 Apple Computer, Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// testutils - utilities for unit test drivers +// +#ifndef _H_TESTUTILS +#define _H_TESTUTILS + +#include "testclient.h" + + +// +// Global test state +// +extern bool verbose; + + +// +// Error and diagnostic drivers +// +void error(const char *fmt, ...) __attribute__((format(printf,1,2))); +void error(const CssmCommonError &error, const char *fmt, ...) __attribute__((format(printf,2,3))); +void detail(const char *fmt, ...) __attribute__((format(printf,1,2))); +void detail(const CssmCommonError &error, const char *msg); +void prompt(const char *msg); +void prompt(); + + +// +// A self-building "fake" context. +// (Fake in that it was hand-made without involvement of CSSM.) +// +class FakeContext : public ::Context { +public: + FakeContext(CSSM_CONTEXT_TYPE type, CSSM_ALGORITHMS alg, uint32 count); + FakeContext(CSSM_CONTEXT_TYPE type, CSSM_ALGORITHMS alg, ...); +}; + + +// +// A test driver class for ACL tests +// +class AclTester { +public: + AclTester(ClientSession &ss, const AclEntryInput *acl); + + void testWrap(const AccessCredentials *cred, const char *howWrong = NULL); + void testEncrypt(const AccessCredentials *cred, const char *howWrong = NULL); + + ClientSession &session; + KeyHandle keyRef; +}; + + +// +// A test driver class for database tests +// +class DbTester { +public: + DbTester(ClientSession &ss, const char *path, + const AccessCredentials *cred, int timeout = 30, bool sleepLock = true); + + operator DbHandle () const { return dbRef; } + void unlock(const char *howWrong = NULL); + void changePassphrase(const AccessCredentials *cred, const char *howWrong = NULL); + + ClientSession &session; + DBParameters params; + DLDbIdentifier dbId; + DbHandle dbRef; +}; + + +#endif //_H_TESTUTILS -- 2.45.2