├── .gitignore ├── CocoaFob.podspec ├── CocoaFob.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── Package.swift ├── README.markdown ├── java ├── .gitignore ├── dist │ └── cocoafob-1.0-SNAPSHOT.jar ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── xk72 │ │ └── cocoafob │ │ ├── LicenseData.java │ │ ├── LicenseGenerator.java │ │ └── LicenseGeneratorException.java │ └── test │ ├── java │ └── com │ │ └── xk72 │ │ └── cocoafob │ │ └── LicenseGeneratorTest.java │ └── resources │ └── com │ └── xk72 │ └── cocoafob │ ├── privkey.pem │ └── pubkey.pem ├── keys ├── dsaparam.pem ├── privkey.pem └── pubkey.pem ├── objc ├── CFobError.h ├── CFobError.m ├── CFobLicGenerator.h ├── CFobLicGenerator.m ├── CFobLicVerifier.h ├── CFobLicVerifier.m ├── CocoaFob-Info.plist ├── CocoaFobARC-Info.plist ├── CocoaFobTests │ ├── CocoaFobTests.m │ └── Info.plist ├── MyApp.scriptSuite ├── MyApp.scriptTerminology ├── NSData+PECrypt.h ├── NSData+PECrypt.m ├── NSString+PECrypt.h ├── NSString+PECrypt.m ├── NSString-Base64Extensions.h ├── NSString-Base64Extensions.m ├── URLCommand.h ├── URLCommand.m ├── cocoafob.1 ├── cocoafob.m ├── cocoafob.xcodeproj │ ├── TemplateIcon.icns │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── decoder.c ├── decoder.h ├── encoder.c ├── encoder.h └── pxlic.m ├── php ├── License │ ├── base32.php │ ├── dsa_priv.pem │ ├── dsa_pub.pem │ ├── generateLicense.php │ ├── license_generator.php │ └── verifylicense.php ├── base32.php └── licensekey.php ├── python ├── cocoafob.py ├── license_server.py └── server.wsgi ├── ruby └── licensekey.rb ├── swift4 ├── CocoaFob.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── CocoaFob.xcscheme ├── CocoaFob │ ├── CFUtil.swift │ ├── CocoaFob.h │ ├── CocoaFobError.swift │ ├── CocoaFobLicGenerator.swift │ ├── CocoaFobLicVerifier.swift │ ├── CocoaFobStringExt.swift │ └── Info.plist ├── CocoaFobTests │ ├── CocoaFobTests.swift │ └── Info.plist ├── README.md ├── cocoafob-keygen │ ├── Functions.swift │ ├── Stderr.swift │ └── main.swift └── vendor │ └── CommandLine │ ├── .gitignore │ ├── .travis.yml │ ├── CommandLine.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── CommandLine │ ├── CommandLine.swift │ ├── Info.plist │ ├── Option.swift │ └── StringExtensions.swift │ ├── CommandLineTests │ ├── CommandLineTests.swift │ ├── Info.plist │ └── StringExtensionTests.swift │ ├── LICENSE │ └── README.md └── swift5 ├── CocoaFob.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── CocoaFob.xcscheme ├── CocoaFob ├── CFUtil.swift ├── CocoaFob.h ├── CocoaFobError.swift ├── Info.plist ├── LicenseGenerator.swift ├── LicenseVerifier.swift └── String+CocoaFob.swift ├── CocoaFobTests ├── CocoaFobTests.swift └── Info.plist ├── README.md ├── cocoafob-keygen ├── Functions.swift ├── Stderr.swift └── main.swift └── vendor └── CommandLine ├── .gitignore ├── .travis.yml ├── CommandLine.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── CommandLine ├── CommandLine.swift ├── Info.plist ├── Option.swift └── StringExtensions.swift ├── CommandLineTests ├── CommandLineTests.swift ├── Info.plist └── StringExtensionTests.swift ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.mode* 3 | *.pbxuser 4 | *.perspectivev3 5 | .idea 6 | xcuserdata 7 | *.xccheckout 8 | *.xcscmblueprint 9 | .build/ 10 | .swiftpm/ 11 | -------------------------------------------------------------------------------- /CocoaFob.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CocoaFob' 3 | s.version = '2.4.0' 4 | s.swift_versions = ["5.7", "5.6", "5.5", "5.4", '5.3', '5.2', '5.1', '5.0'] 5 | s.summary = 'macOS app registration code verification & generation.' 6 | s.description = <<-DESC 7 | CocoaFob is a set of helper code snippets for registration code generation and 8 | verification in Cocoa applications, integrated with registration code 9 | generation in Potion Store and 10 | FastSpring . 11 | DESC 12 | 13 | s.homepage = 'https://github.com/glebd/cocoafob' 14 | s.license = 'BSD' 15 | s.author = { 'Gleb Dolgich' => '@glebd' } 16 | s.source = { :git => 'https://github.com/glebd/cocoafob.git', :tag => s.version } 17 | 18 | s.module_name = 'CocoaFob' 19 | s.platform = :osx 20 | s.osx.deployment_target = '10.13' 21 | 22 | s.source_files = ['swift5/CocoaFob/*.swift'] 23 | end 24 | -------------------------------------------------------------------------------- /CocoaFob.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CocoaFob.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CocoaFob is distributed under the BSD License 2 | 3 | 4 | Copyright (c) 2009-2016, PixelEspresso . All rights reserved. 5 | Written by Gleb Dolgich (@glebd) and contributors. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. Redistributions in binary form 12 | must reproduce the above copyright notice, this list of conditions and the 13 | following disclaimer in the documentation and/or other materials provided with 14 | the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.5 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "CocoaFob", 7 | platforms: [ 8 | .macOS(.v10_13), 9 | ], 10 | products: [ 11 | .library( 12 | name: "CocoaFob", 13 | type: .static, 14 | targets: ["CocoaFob"]), 15 | ], 16 | dependencies: [ 17 | ], 18 | targets: [ 19 | .target( 20 | name: "CocoaFob", 21 | dependencies: [], 22 | path: "swift5/CocoaFob", 23 | exclude: ["Info.plist"]), 24 | .testTarget( 25 | name: "CocoaFobTests", 26 | dependencies: ["CocoaFob"], 27 | path: "swift5/CocoaFobTests", 28 | exclude: ["Info.plist"]), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | CocoaFob is a set of helper code snippets for registration code generation and 4 | verification in Objective-C applications, integrated with registration code 5 | generation in Potion Store and 6 | FastSpring . 7 | 8 | The current implementation uses DSA to generate registration keys, which 9 | significantly reduces chances of crackers producing key generators for your 10 | software. Unfortunately, it also means the registration code can be quite long 11 | and has variable length. 12 | 13 | To make registration codes human-readable, CocoaFob encodes them using a 14 | slightly modified base32 to avoid ambiguous characters. It also groups codes in 15 | sets of five characters separated by dashes. A sample registration code 16 | produced using a 512-bit DSA key looks like this: 17 | 18 | `GAWQE-FCUGU-7Z5JE-WEVRA-PSGEQ-Y25KX-9ZJQQ-GJTQC-CUAJL-ATBR9-WV887-8KAJM-QK7DT-EZHXJ-CR99C-A` 19 | 20 | One of the advantages of DSA is that for a given registration name, each 21 | generated code is different, as there is a random element introduced during the 22 | process. 23 | 24 | The name 'CocoaFob' is a combination of 'Cocoa' (the Mac and iOS programming 25 | framework) and 'Fob' (a key fob is something you keep your keys on). 26 | 27 | # Features 28 | 29 | CocoaFob provides the following for your application: 30 | 31 | - Secure asymmetric cryptography-based registration key generation and 32 | verification using DSA. 33 | 34 | - Support for key generation in Objective-C and Ruby and verification in 35 | Objective-C for integration in both your Cocoa application and Potion Store. 36 | 37 | - Support for custom URL scheme for automatic application registration. 38 | 39 | There is no framework or a library to link against. You include the files you 40 | need in your application project directly and are free to modify the code in 41 | any way you need. 42 | 43 | You may also find other snippets of code useful, such as base32 and base64 44 | encoding/decoding functions, as well as categories extending `NSString` and 45 | `NSData` classes with base32 and base64 methods. 46 | 47 | # Usage 48 | 49 | The best way to get the latest version of the code is to clone the main Git 50 | repository: 51 | 52 | `git://github.com/glebd/cocoafob.git` 53 | 54 | You can also download the latest version from the CocoaFob home page at 55 | . 56 | 57 | For more complete examples of how to use CocoaFob, look at the following 58 | projects by Alex Clarke: CodexFab 59 | and LicenseExample . 60 | 61 | ## Providing a Registration Source String 62 | 63 | To register an application that uses CocoaFob, it is necessary to provide a 64 | string of source information, which may be as simple as a registration name 65 | or arbitrarily complex in case your application is processing the included 66 | information in a user-friendly way. For example, as suggested in the sample 67 | implementation of Potion Store licence generator, a source string may contain 68 | application name, user name and number of copies: 69 | 70 | `myapp|Joe Bloggs|1` 71 | 72 | When sending registration mail, you need to provide both the source string and 73 | the registration code. Potion Store does this for you. However, small 74 | modifications are needed to make automatic activation work. 75 | 76 | ## Generating automatic activation URLs 77 | 78 | Potion Store supports automatic activation of an installed application by 79 | clicking on a special link in a registration email or on the Thank You store 80 | page. For this to work, you need to: 81 | 82 | - make your application support a registration URL scheme; 83 | 84 | - modify Potion Store so that automatic activation link contains not only 85 | registration code, but registration source string as well. 86 | 87 | The stock implementation of Potion Store registration code support assumes a 88 | registration code is all that is needed to register an application. However, 89 | CocoaFob needs to know both registration name and registration code in order 90 | to verify the licence. This means when Potion Store generates an automatic 91 | registration URL for your application, it needs to include registration source 92 | string in it. One of the possible solutions is as follows: 93 | 94 | - In your database migration `001_create_tables.rb`, increase the length of 95 | `license_key` column in `line_items` table to 128 characters: 96 | 97 | ```ruby 98 | t.column "license_key", :string, :limit => 128 99 | ``` 100 | 101 | - In the file `app/models/line_item.rb`, add the following line at the top: 102 | 103 | ```ruby 104 | require "base64"` 105 | ``` 106 | 107 | - In the same file find function called `license_url` near the bottom of the 108 | file. Replace it with the following (or modify to your heart's content): 109 | 110 | ```ruby 111 | def license_url 112 | licensee_name_b64 = Base64.encode64(self.order.licensee_name) 113 | return "#{self.product.license_url_scheme}://#{licensee_name_b64}/#{self.license_key}" rescue nil 114 | end 115 | ``` 116 | 117 | This will make generated registration codes to contain base64-encoded licensee 118 | name. When your application is opened by clicking on the registration link, it 119 | will parse the code, extract the registration name and use it to verify the 120 | licence. 121 | 122 | ## Supporting registration URL schema in your app 123 | 124 | The following files in objc directory provide a sample implementation of 125 | support for custom URL schema for application registration. The code is almost 126 | literally taken from [3]. 127 | 128 | To support registration URLs in your application: 129 | 130 | - Add files `MyApp.scriptSuite` and `MyApp.scriptTerminology` to your project's 131 | resources, adjusting strings inside appropriately. 132 | 133 | - Add the following to your application's `Info.plist` file under `/plist/dict` 134 | key (replace *mycompany* and *myapp* with strings appropriate for your company 135 | and application): 136 | 137 | ```xml 138 | NSAppleScriptEnabled 139 | YES 140 | CFBundleURLTypes 141 | 142 | 143 | CFBundleURLSchemes 144 | 145 | com.mycompany.myapp.lic 146 | 147 | 148 | 149 | ``` 150 | 151 | - Add the files `URLCommand.h` and `URLCommand.m` to your project, paying 152 | attention to the `TODO:` comments in them. Specifically, you may want to save 153 | registration information to your application's preferences, and also 154 | broadcast a notification of a changed registration information after 155 | verifying the supplied registration URL. 156 | 157 | - Be sure the URL scheme name in the `Info.plist` file 158 | (`com.mycompany.myapp.lic`) is the same as the one in the database generation 159 | script for Potion Store. It is the file `db/migrate/001_create_tables.rb`, 160 | and the variable is called `license_url_scheme`. 161 | 162 | Test the URL schema support by making a test purchase which results in 163 | displaying an activation link, and clicking on it. If you are running your 164 | application in debugger, place a breakpoint in the instance method 165 | `performWithURL:` of the class `URLCommand`. The breakpoint will be triggered 166 | when you click on the registration link. You can extract the link into a 167 | standalone HTML file so that is available for testing without making any 168 | additional test purchases. 169 | 170 | # Generating Keys 171 | 172 | IMPORTANT NOTE: Included keys are for demonstration and testing purposes only. 173 | DO NOT USE THE INCLUDED KEYS IN YOUR SOFTWARE. Before incorporating CocoaFob 174 | into your application, you need to generate a pair of your own DSA keys. I used 175 | key length of 512 bit which I thought was enough for the registration code 176 | generation purposes. 177 | 178 | (0) Make sure OpenSSL is installed. (If you're using Mac OS X, it already is.) 179 | 180 | (1) Generate DSA parameters: 181 | 182 | openssl dsaparam -out dsaparam.pem 512 183 | 184 | (2) Generate an unencrypted DSA private key: 185 | 186 | openssl gendsa -out privkey.pem dsaparam.pem 187 | 188 | (3) Extract public key from private key: 189 | 190 | openssl dsa -in privkey.pem -pubout -out pubkey.pem 191 | 192 | See [2] for more information. 193 | 194 | # Licence 195 | 196 | CocoaFob is distributed under the BSD License 197 | . See the [LICENSE](LICENSE) file. 198 | 199 | # Credits 200 | 201 | [0] The Mac developer community that continues to amaze me. 202 | 203 | [1] Base32 implementation is Copyright © 2007 by Samuel Tesla and comes from 204 | Ruby base32 gem: . 205 | 206 | [2] OpenSSL key generation HOWTO: 207 | 208 | [3] Handling URL schemes in Cocoa: a blog post by Kimbro Staken 209 | 210 | [4] Registering a protocol handler for an App: a post on CocoaBuilder mailing 211 | list 212 | 213 | [5] PHP implementation courtesy of Sandro Noel 214 | 215 | [6] Security framework-based implementation by Matt Stevens, 216 | 217 | [7] New API by Danny Greg, 218 | -------------------------------------------------------------------------------- /java/.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.classpath 3 | /.settings 4 | /target 5 | -------------------------------------------------------------------------------- /java/dist/cocoafob-1.0-SNAPSHOT.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glebd/cocoafob/7903c431e4838861afb29b8b6f51d445fa552072/java/dist/cocoafob-1.0-SNAPSHOT.jar -------------------------------------------------------------------------------- /java/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.xk72 6 | cocoafob 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | cocoafob 11 | https://github.com/glebd/cocoafob/ 12 | 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | junit 20 | junit 21 | 4.13.1 22 | test 23 | 24 | 25 | commons-codec 26 | commons-codec 27 | 1.9 28 | 29 | 30 | org.bouncycastle 31 | bcprov-jdk16 32 | 1.46 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /java/src/main/java/com/xk72/cocoafob/LicenseData.java: -------------------------------------------------------------------------------- 1 | package com.xk72.cocoafob; 2 | 3 | /** 4 | * Represents the data used as the string data input to the CocoaFob algorithm. Extend this class 5 | * and override {@link LicenseData#toLicenseStringData()} to customise the string data to match your application. 6 | * @author karlvr 7 | * 8 | */ 9 | public class LicenseData { 10 | 11 | protected String productCode; 12 | protected String name; 13 | protected String email; 14 | 15 | protected LicenseData() { 16 | super(); 17 | } 18 | 19 | public LicenseData(String productCode, String name) { 20 | super(); 21 | this.productCode = productCode; 22 | this.name = name; 23 | } 24 | 25 | public LicenseData(String productCode, String name, String email) { 26 | super(); 27 | this.productCode = productCode; 28 | this.name = name; 29 | this.email = email; 30 | } 31 | 32 | public String getProductCode() { 33 | return productCode; 34 | } 35 | 36 | public void setProductCode(String productCode) { 37 | this.productCode = productCode; 38 | } 39 | 40 | public String getName() { 41 | return name; 42 | } 43 | 44 | public void setName(String name) { 45 | this.name = name; 46 | } 47 | 48 | public String getEmail() { 49 | return email; 50 | } 51 | 52 | public void setEmail(String email) { 53 | this.email = email; 54 | } 55 | 56 | /** 57 | * Returns the string data input for the CocoaFob algorithm. This implementation returns a comma separated string 58 | * including the {@link #productCode}, {@link #name} and {@link #email} if set. 59 | * @return 60 | */ 61 | public String toLicenseStringData() { 62 | StringBuilder result = new StringBuilder(); 63 | result.append(productCode); 64 | result.append(','); 65 | result.append(name); 66 | if (email != null) { 67 | result.append(','); 68 | result.append(email); 69 | } 70 | return result.toString(); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /java/src/main/java/com/xk72/cocoafob/LicenseGenerator.java: -------------------------------------------------------------------------------- 1 | package com.xk72.cocoafob; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.URL; 9 | import java.security.InvalidKeyException; 10 | import java.security.KeyPair; 11 | import java.security.NoSuchAlgorithmException; 12 | import java.security.NoSuchProviderException; 13 | import java.security.SecureRandom; 14 | import java.security.Security; 15 | import java.security.Signature; 16 | import java.security.SignatureException; 17 | import java.security.interfaces.DSAPrivateKey; 18 | import java.security.interfaces.DSAPublicKey; 19 | 20 | import org.apache.commons.codec.binary.Base32; 21 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 22 | import org.bouncycastle.openssl.PEMReader; 23 | 24 | /** 25 | * Generate and verify CocoaFob licenses. Based on the PHP implementation by Sandro Noel. 26 | * @author karlvr 27 | * 28 | */ 29 | public class LicenseGenerator { 30 | 31 | private DSAPrivateKey privateKey; 32 | private DSAPublicKey publicKey; 33 | private SecureRandom random; 34 | 35 | static { 36 | Security.addProvider(new BouncyCastleProvider()); 37 | } 38 | 39 | protected LicenseGenerator() { 40 | random = new SecureRandom(); 41 | } 42 | 43 | /** 44 | * Construct the LicenseGenerator with a URL that points to either the private key or public key. 45 | * Pass the private key for making and verifying licenses. Pass the public key for verifying only. 46 | * If you this code will go onto a user's machine you MUST NOT include the private key, only include 47 | * the public key in this case. 48 | * @param keyURL 49 | * @throws IOException 50 | */ 51 | public LicenseGenerator(URL keyURL) throws IOException { 52 | this(); 53 | initKeys(keyURL.openStream()); 54 | } 55 | 56 | /** 57 | * Construct the LicenseGenerator with an InputStream of either the private key or public key. 58 | * Pass the private key for making and verifying licenses. Pass the public key for verifying only. 59 | * If you this code will go onto a user's machine you MUST NOT include the private key, only include 60 | * the public key in this case. 61 | * @param keyURL 62 | * @throws IOException 63 | */ 64 | public LicenseGenerator(InputStream keyInputStream) throws IOException { 65 | this(); 66 | initKeys(keyInputStream); 67 | } 68 | 69 | private void initKeys(InputStream keyInputStream) throws IOException { 70 | Object readKey = readKey(keyInputStream); 71 | if (readKey instanceof KeyPair) { 72 | KeyPair keyPair = (KeyPair) readKey; 73 | privateKey = (DSAPrivateKey) keyPair.getPrivate(); 74 | publicKey = (DSAPublicKey) keyPair.getPublic(); 75 | } else if (readKey instanceof DSAPublicKey) { 76 | publicKey = (DSAPublicKey) readKey; 77 | } else { 78 | throw new IllegalArgumentException("The supplied key stream didn't contain a public or private key: " + readKey.getClass()); 79 | } 80 | } 81 | 82 | private Object readKey(InputStream privateKeyInputSteam) throws IOException { 83 | PEMReader pemReader = new PEMReader(new InputStreamReader(new BufferedInputStream(privateKeyInputSteam))); 84 | try { 85 | return pemReader.readObject(); 86 | } finally { 87 | pemReader.close(); 88 | } 89 | } 90 | 91 | /** 92 | * Make and return a license for the given {@link LicenseData}. 93 | * @param licenseData 94 | * @return 95 | * @throws LicenseGeneratorException If the generation encounters an error, usually due to invalid input. 96 | * @throws IllegalStateException If the generator is not setup correctly to make licenses. 97 | */ 98 | public String makeLicense(LicenseData licenseData) throws LicenseGeneratorException, IllegalStateException { 99 | if (!isCanMakeLicenses()) { 100 | throw new IllegalStateException("The LicenseGenerator cannot make licenses as it was not configured with a private key"); 101 | } 102 | 103 | final String stringData = licenseData.toLicenseStringData(); 104 | 105 | try { 106 | final Signature dsa = Signature.getInstance("SHA1withDSA", "SUN"); 107 | dsa.initSign(privateKey, random); 108 | dsa.update(stringData.getBytes("UTF-8")); 109 | 110 | final byte[] signed = dsa.sign(); 111 | 112 | /* base 32 encode the signature */ 113 | String result = new Base32().encodeAsString(signed); 114 | 115 | /* replace O with 8 and I with 9 */ 116 | result = result.replace("O", "8").replace("I", "9"); 117 | 118 | /* remove padding if any. */ 119 | result = result.replace("=", ""); 120 | 121 | /* chunk with dashes */ 122 | result = split(result, 5); 123 | return result; 124 | } catch (NoSuchAlgorithmException e) { 125 | throw new LicenseGeneratorException(e); 126 | } catch (NoSuchProviderException e) { 127 | throw new LicenseGeneratorException(e); 128 | } catch (InvalidKeyException e) { 129 | throw new LicenseGeneratorException(e); 130 | } catch (SignatureException e) { 131 | throw new LicenseGeneratorException(e); 132 | } catch (UnsupportedEncodingException e) { 133 | throw new LicenseGeneratorException(e); 134 | } 135 | } 136 | 137 | /** 138 | * Verify the given license for the given {@link LicenseData}. 139 | * @param licenseData 140 | * @param license 141 | * @return Whether the license verified successfully. 142 | * @throws LicenseGeneratorException If the verification encounters an error, usually due to invalid input. You MUST check the return value of this method if no exception is thrown. 143 | * @throws IllegalStateException If the generator is not setup correctly to verify licenses. 144 | */ 145 | public boolean verifyLicense(LicenseData licenseData, String license) throws LicenseGeneratorException, IllegalStateException { 146 | if (!isCanVerifyLicenses()) { 147 | throw new IllegalStateException("The LicenseGenerator cannot verify licenses as it was not configured with a public key"); 148 | } 149 | 150 | final String stringData = licenseData.toLicenseStringData(); 151 | 152 | /* replace O with 8 and I with 9 */ 153 | String licenseSignature = license.replace("8", "O").replace("9", "I"); 154 | 155 | /* remove dashes */ 156 | licenseSignature = licenseSignature.replace("-", ""); 157 | 158 | /* Pad the output length to a multiple of 8 with '=' characters */ 159 | while (licenseSignature.length() % 8 != 0) { 160 | licenseSignature += "="; 161 | } 162 | 163 | byte[] decoded = new Base32().decode(licenseSignature); 164 | try { 165 | Signature dsa = Signature.getInstance("SHA1withDSA", "SUN"); 166 | dsa.initVerify(publicKey); 167 | dsa.update(stringData.getBytes("UTF-8")); 168 | return dsa.verify(decoded); 169 | } catch (NoSuchAlgorithmException e) { 170 | throw new LicenseGeneratorException(e); 171 | } catch (NoSuchProviderException e) { 172 | throw new LicenseGeneratorException(e); 173 | } catch (InvalidKeyException e) { 174 | throw new LicenseGeneratorException(e); 175 | } catch (SignatureException e) { 176 | throw new LicenseGeneratorException(e); 177 | } catch (UnsupportedEncodingException e) { 178 | throw new LicenseGeneratorException(e); 179 | } 180 | } 181 | 182 | private String split(String str, int chunkSize) { 183 | StringBuilder result = new StringBuilder(); 184 | int i = 0; 185 | while (i < str.length()) { 186 | if (i > 0) { 187 | result.append('-'); 188 | } 189 | int next = Math.min(i + chunkSize, str.length()); 190 | result.append(str.substring(i, next)); 191 | i = next; 192 | } 193 | return result.toString(); 194 | } 195 | 196 | public boolean isCanMakeLicenses() { 197 | return privateKey != null; 198 | } 199 | 200 | public boolean isCanVerifyLicenses() { 201 | return publicKey != null; 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /java/src/main/java/com/xk72/cocoafob/LicenseGeneratorException.java: -------------------------------------------------------------------------------- 1 | package com.xk72.cocoafob; 2 | 3 | /** 4 | * An error occurred in the license generation or verification. This generally means that the input 5 | * was malformed somehow and should be rejected. 6 | * @author karlvr 7 | * 8 | */ 9 | public class LicenseGeneratorException extends Exception { 10 | 11 | public LicenseGeneratorException() { 12 | super(); 13 | } 14 | 15 | public LicenseGeneratorException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public LicenseGeneratorException(String message) { 20 | super(message); 21 | } 22 | 23 | public LicenseGeneratorException(Throwable cause) { 24 | super(cause); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /java/src/test/java/com/xk72/cocoafob/LicenseGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package com.xk72.cocoafob; 2 | 3 | import java.io.IOException; 4 | 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | public class LicenseGeneratorTest { 9 | 10 | @Test 11 | public void testPrivateKey() throws IOException { 12 | LicenseGenerator lg = new LicenseGenerator(getClass().getResource("privkey.pem")); 13 | Assert.assertTrue(lg.isCanMakeLicenses()); 14 | Assert.assertTrue(lg.isCanVerifyLicenses()); 15 | } 16 | 17 | @Test 18 | public void testPublicKey() throws IOException { 19 | LicenseGenerator lg = new LicenseGenerator(getClass().getResource("pubkey.pem")); 20 | Assert.assertFalse(lg.isCanMakeLicenses()); 21 | Assert.assertTrue(lg.isCanVerifyLicenses()); 22 | } 23 | 24 | @Test 25 | public void testMakeLicense() throws IOException, IllegalStateException, LicenseGeneratorException { 26 | LicenseGenerator lg = new LicenseGenerator(getClass().getResource("privkey.pem")); 27 | String license = lg.makeLicense(new LicenseData("Test", "Karl", "karl@example.com")); 28 | Assert.assertTrue(license.length() > 0); 29 | } 30 | 31 | @Test 32 | public void testVerifyLicense() throws IOException, IllegalStateException, LicenseGeneratorException { 33 | LicenseGenerator lg = new LicenseGenerator(getClass().getResource("privkey.pem")); 34 | LicenseData licenseData = new LicenseData("Test", "Karl", "karl@example.com"); 35 | String license = lg.makeLicense(licenseData); 36 | boolean verified = lg.verifyLicense(licenseData, license); 37 | Assert.assertTrue(verified); 38 | } 39 | 40 | @Test 41 | public void testVerifyLicense2() throws IOException, IllegalStateException, LicenseGeneratorException { 42 | LicenseGenerator lg = new LicenseGenerator(getClass().getResource("privkey.pem")); 43 | LicenseData licenseData = new LicenseData("Test", "Karl"); 44 | String license = lg.makeLicense(licenseData); 45 | boolean verified = lg.verifyLicense(licenseData, license); 46 | Assert.assertTrue(verified); 47 | } 48 | 49 | @Test 50 | public void testFailedVerifyLicense() throws IOException, IllegalStateException, LicenseGeneratorException { 51 | LicenseGenerator lg = new LicenseGenerator(getClass().getResource("privkey.pem")); 52 | LicenseData licenseData = new LicenseData("Test", "Karl"); 53 | Assert.assertTrue(lg.verifyLicense(licenseData, "GAWQE-F9AVF-8YSF3-NBDUH-C6M2J-JYAYC-X692H-H65KR-A9KAQ-R9SB7-A374H-T6AH3-87TAB-CVV6K-SKUGG-A")); 54 | Assert.assertFalse(lg.verifyLicense(licenseData, "GAWQE-F9AVF-8YSF3-NBDUH-C6M2J-JYAYC-X692H-H65KR-A9KAQ-R9SB7-A374H-T6AH3-87TAB-CVV6K-SKAGG-A")); 55 | Assert.assertFalse(lg.verifyLicense(licenseData, "GAWQE-F9AVF-8YSF3-NBDUH-C6M2J-JYAYC-X692H-H65KR-A9KAQ-R9SB7-A374H-T6AH3-87TAB-DVV6K-SKUGG-A")); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /java/src/test/resources/com/xk72/cocoafob/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIH5AgEAAkEA8wm04e0QcQRoAVJWWnUw/4rQEKbLKjujJu6oyEv7Y2oT3itY5pbO 3 | bgYCHEu9FBizqq7apsWYSF3YXiRjKlg10wIVALfs9eVL10PhoV6zczFpi3C7FzWN 4 | AkBaPhALEKlgIltHsumHdTSBqaVoR1/bmlgw/BCC13IAsW40nkFNsK1OVwjo2ocn 5 | 3MwW4Rdq6uLm3DlENRZ5bYrTAkEA4reDYZKAl1vx+8EIMP/+2Z7ekydHfX0sTMDg 6 | kxhtRm6qtcywg01X847Y9ySgNepqleD+Ka2Wbucj1pOry8MoDQIVAIXgAB9GBLh4 7 | keUwLHBtpClnD5E8 8 | -----END DSA PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /java/src/test/resources/com/xk72/cocoafob/pubkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIHxMIGoBgcqhkjOOAQBMIGcAkEA8wm04e0QcQRoAVJWWnUw/4rQEKbLKjujJu6o 3 | yEv7Y2oT3itY5pbObgYCHEu9FBizqq7apsWYSF3YXiRjKlg10wIVALfs9eVL10Ph 4 | oV6zczFpi3C7FzWNAkBaPhALEKlgIltHsumHdTSBqaVoR1/bmlgw/BCC13IAsW40 5 | nkFNsK1OVwjo2ocn3MwW4Rdq6uLm3DlENRZ5bYrTA0QAAkEA4reDYZKAl1vx+8EI 6 | MP/+2Z7ekydHfX0sTMDgkxhtRm6qtcywg01X847Y9ySgNepqleD+Ka2Wbucj1pOr 7 | y8MoDQ== 8 | -----END PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /keys/dsaparam.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PARAMETERS----- 2 | MIGcAkEA8wm04e0QcQRoAVJWWnUw/4rQEKbLKjujJu6oyEv7Y2oT3itY5pbObgYC 3 | HEu9FBizqq7apsWYSF3YXiRjKlg10wIVALfs9eVL10PhoV6zczFpi3C7FzWNAkBa 4 | PhALEKlgIltHsumHdTSBqaVoR1/bmlgw/BCC13IAsW40nkFNsK1OVwjo2ocn3MwW 5 | 4Rdq6uLm3DlENRZ5bYrT 6 | -----END DSA PARAMETERS----- 7 | -------------------------------------------------------------------------------- /keys/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIH5AgEAAkEA8wm04e0QcQRoAVJWWnUw/4rQEKbLKjujJu6oyEv7Y2oT3itY5pbO 3 | bgYCHEu9FBizqq7apsWYSF3YXiRjKlg10wIVALfs9eVL10PhoV6zczFpi3C7FzWN 4 | AkBaPhALEKlgIltHsumHdTSBqaVoR1/bmlgw/BCC13IAsW40nkFNsK1OVwjo2ocn 5 | 3MwW4Rdq6uLm3DlENRZ5bYrTAkEA4reDYZKAl1vx+8EIMP/+2Z7ekydHfX0sTMDg 6 | kxhtRm6qtcywg01X847Y9ySgNepqleD+Ka2Wbucj1pOry8MoDQIVAIXgAB9GBLh4 7 | keUwLHBtpClnD5E8 8 | -----END DSA PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /keys/pubkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIHxMIGoBgcqhkjOOAQBMIGcAkEA8wm04e0QcQRoAVJWWnUw/4rQEKbLKjujJu6o 3 | yEv7Y2oT3itY5pbObgYCHEu9FBizqq7apsWYSF3YXiRjKlg10wIVALfs9eVL10Ph 4 | oV6zczFpi3C7FzWNAkBaPhALEKlgIltHsumHdTSBqaVoR1/bmlgw/BCC13IAsW40 5 | nkFNsK1OVwjo2ocn3MwW4Rdq6uLm3DlENRZ5bYrTA0QAAkEA4reDYZKAl1vx+8EI 6 | MP/+2Z7ekydHfX0sTMDgkxhtRm6qtcywg01X847Y9ySgNepqleD+Ka2Wbucj1pOr 7 | y8MoDQ== 8 | -----END PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /objc/CFobError.h: -------------------------------------------------------------------------------- 1 | // 2 | // CFobError.h 3 | // cocoafob 4 | // 5 | // Created by Danny Greg on 24/08/2010. 6 | // Copyright 2010 Realmac Software. All rights reserved. 7 | // Licensed under CC Attribution Licence 3.0 8 | // 9 | 10 | #import 11 | 12 | enum _CFobErrorCode { 13 | CFobErrorCodeInvalidKey = -1, 14 | CFobErrorCodeCouldNotDecode = -2, 15 | CFobErrorCodeSigningFailed = -3, 16 | CFobErrorCodeCouldNotEncode = -4, 17 | CFobErrorCodeNoName = -5, 18 | }; 19 | 20 | void CFobAssignErrorWithDescriptionAndCode(NSError **err, NSString *description, NSInteger code); 21 | -------------------------------------------------------------------------------- /objc/CFobError.m: -------------------------------------------------------------------------------- 1 | // 2 | // CFobError.h 3 | // cocoafob 4 | // 5 | // Created by Danny Greg on 24/08/2010. 6 | // Copyright 2010 Realmac Software. All rights reserved. 7 | // Licensed under CC Attribution Licence 3.0 8 | // 9 | 10 | #import "CFobError.h" 11 | 12 | #import "CFobLicVerifier.h" 13 | 14 | void CFobAssignErrorWithDescriptionAndCode(NSError **err, NSString *description, NSInteger code) 15 | { 16 | if (err != NULL) 17 | *err = [NSError errorWithDomain:[[NSBundle bundleForClass:[CFobLicVerifier class]] bundleIdentifier] code:code userInfo:[NSDictionary dictionaryWithObject:NSLocalizedStringFromTableInBundle(description, nil, [NSBundle bundleForClass:[CFobLicVerifier class]], nil) forKey:NSLocalizedDescriptionKey]]; 18 | } 19 | -------------------------------------------------------------------------------- /objc/CFobLicGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // CFobLicGenerator.h 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 09/02/2009. 6 | // Follow me on Twitter @glebd. 7 | // Copyright (C) 2009-2015 PixelEspresso. All rights reserved. 8 | // BSD License 9 | // 10 | 11 | #import 12 | #import 13 | 14 | /*! 15 | @class CFobLicGenerator 16 | @superclass NSObject 17 | @abstract Generates CocoaFob-style registration codes. 18 | @discussion Given user name and DSA private key, generates a human-readable registration code. 19 | */ 20 | @interface CFobLicGenerator : NSObject { 21 | SecKeyRef _privateKey; 22 | } 23 | 24 | /*! 25 | @method setPrivateKey: 26 | @abstract Sets a new DSA private key. 27 | @discussion Sets a new DSA private key to be used for subsequent generated registration codes. 28 | @param privKey PEM-encoded non-encrypted DSA private key. 29 | @result YES on success, NO on error. 30 | */ 31 | - (BOOL)setPrivateKey:(NSString *)privKey error:(NSError **)err; 32 | 33 | /*! 34 | @method generate 35 | @abstract Generates a registration code from regName property. 36 | @discussion Takes regName property and DSA private key and generates a new registration code that is placed in regCode property. 37 | @param The name or registration string to generate a serial number for. 38 | @result The serial number as a string, nil on failure. 39 | */ 40 | - (NSString *)generateRegCodeForName:(NSString *)name error:(NSError **)err; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /objc/CFobLicGenerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // CFobLicGenerator.m 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 09/02/2009. 6 | // Follow me on Twitter @glebd. 7 | // Copyright (C) 2009-2015 PixelEspresso. All rights reserved. 8 | // BSD License 9 | // 10 | 11 | #import "CFobLicGenerator.h" 12 | #import "CFobError.h" 13 | 14 | @interface CFobLicGenerator () 15 | @property (retain) __attribute__((NSObject)) SecKeyRef privateKey; 16 | @end 17 | 18 | 19 | @implementation CFobLicGenerator 20 | 21 | @synthesize privateKey = _privateKey; 22 | 23 | #pragma mark - 24 | #pragma mark Lifecycle 25 | 26 | #if !__has_feature(objc_arc) 27 | - (void)finalize 28 | { 29 | self.privateKey = nil; 30 | [super finalize]; 31 | } 32 | 33 | - (void)dealloc 34 | { 35 | [super dealloc]; 36 | } 37 | #endif 38 | 39 | #pragma mark - 40 | #pragma mark API 41 | 42 | - (BOOL)setPrivateKey:(NSString *)privKey error:(NSError **)err 43 | { 44 | self.privateKey = nil; 45 | 46 | // Validate the argument. 47 | if (privKey == nil || [privKey length] < 1) { 48 | CFobAssignErrorWithDescriptionAndCode(err, @"Invalid private key.", CFobErrorCodeInvalidKey); 49 | return NO; 50 | } 51 | 52 | SecItemImportExportKeyParameters params = {}; 53 | SecExternalItemType keyType = kSecItemTypePrivateKey; 54 | SecExternalFormat keyFormat = kSecFormatPEMSequence; 55 | CFArrayRef importArray = NULL; 56 | 57 | NSData *privKeyData = [privKey dataUsingEncoding:NSUTF8StringEncoding]; 58 | #if __has_feature(objc_arc) 59 | CFDataRef privKeyDataRef = (__bridge CFDataRef)privKeyData; 60 | #else 61 | CFDataRef privKeyDataRef = (CFDataRef)privKeyData; 62 | #endif 63 | 64 | OSStatus importError = SecItemImport(privKeyDataRef, NULL, &keyFormat, &keyType, 0, ¶ms, NULL, &importArray); 65 | if (importError) { 66 | CFobAssignErrorWithDescriptionAndCode(err, @"Unable to decode key.", CFobErrorCodeCouldNotDecode); 67 | if (importArray) { 68 | CFRelease(importArray); 69 | } 70 | return NO; 71 | } 72 | 73 | self.privateKey = (SecKeyRef)CFArrayGetValueAtIndex(importArray, 0); 74 | CFRelease(importArray); 75 | return YES; 76 | } 77 | 78 | - (NSString *)generateRegCodeForName:(NSString *)name error:(NSError **)err 79 | { 80 | if (name == nil || [name length] < 1) { 81 | CFobAssignErrorWithDescriptionAndCode(err, @"No name provided.", CFobErrorCodeNoName); 82 | return nil; 83 | } 84 | 85 | if (!self.privateKey) { 86 | CFobAssignErrorWithDescriptionAndCode(err, @"Invalid private key.", CFobErrorCodeInvalidKey); 87 | return nil; 88 | } 89 | 90 | NSData *keyData = nil; 91 | NSData *nameData = [name dataUsingEncoding:NSUTF8StringEncoding]; 92 | #if __has_feature(objc_arc) 93 | CFDataRef nameDataRef = (__bridge CFDataRef)nameData; 94 | #else 95 | CFDataRef nameDataRef = (CFDataRef)nameData; 96 | #endif 97 | 98 | SecGroupTransformRef group = SecTransformCreateGroupTransform(); 99 | SecTransformRef signer = SecSignTransformCreate(self.privateKey, NULL); 100 | if (signer) { 101 | SecTransformSetAttribute(signer, kSecTransformInputAttributeName, nameDataRef, NULL); 102 | SecTransformSetAttribute(signer, kSecDigestTypeAttribute, kSecDigestSHA1, NULL); 103 | SecTransformRef encoder = SecEncodeTransformCreate(kSecBase32Encoding, NULL); 104 | if (encoder) { 105 | SecTransformConnectTransforms(signer, kSecTransformOutputAttributeName, encoder, kSecTransformInputAttributeName, group, NULL); 106 | #if __has_feature(objc_arc) 107 | keyData = (NSData *)CFBridgingRelease(SecTransformExecute(group, NULL)); 108 | #else 109 | keyData = [(NSData *)SecTransformExecute(group, NULL) autorelease]; 110 | #endif 111 | CFRelease(encoder); 112 | } 113 | CFRelease(signer); 114 | } 115 | CFRelease(group); 116 | 117 | if (!keyData) { 118 | CFobAssignErrorWithDescriptionAndCode(err, @"Signing failed.", CFobErrorCodeSigningFailed); 119 | return nil; 120 | } 121 | 122 | NSString *b32Orig = [[NSString alloc] initWithData:keyData encoding:NSUTF8StringEncoding]; 123 | #if !__has_feature(objc_arc) 124 | [b32Orig autorelease]; 125 | #endif 126 | 127 | // Replace Os with 8s and Is with 9s 128 | NSString *replacedOWith8 = [b32Orig stringByReplacingOccurrencesOfString:@"O" withString:@"8"]; 129 | NSString *b32 = [replacedOWith8 stringByReplacingOccurrencesOfString:@"I" withString:@"9"]; 130 | 131 | // Cut off the padding. 132 | NSString *regKeyNoPadding = [b32 stringByReplacingOccurrencesOfString:@"=" withString:@""]; 133 | 134 | // Add dashes every 5 characters. 135 | NSMutableString *serial = [NSMutableString stringWithString:regKeyNoPadding]; 136 | NSUInteger index = 5; 137 | while (index < [serial length]) { 138 | [serial insertString:@"-" atIndex:index]; 139 | index += 6; 140 | } 141 | 142 | return serial; 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /objc/CFobLicVerifier.h: -------------------------------------------------------------------------------- 1 | // 2 | // CFobLicVerifier.h 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 06/02/2009. 6 | // Follow me on Twitter @glebd. 7 | // Copyright 2009-2015 PixelEspresso. All rights reserved. 8 | // Licensed under BSD license. 9 | // 10 | 11 | #import 12 | #import 13 | 14 | /*! 15 | @class CFobLicVerifier 16 | @superclass NSObject 17 | @abstract Verifies CocoaFob-style registration key. 18 | @discussion Verifies CocoaFob-style registration key given licensing information (for example, application name, user name, and number of copies as suggested in Potion Store) and signature in human-readable format. A signature is a base32-encoded bignum with padding removed and dashes inserted. 19 | */ 20 | @interface CFobLicVerifier : NSObject { 21 | SecKeyRef _publicKey; 22 | NSArray *_blacklist; 23 | } 24 | 25 | @property (nonatomic, copy) NSArray *blacklist; 26 | 27 | /*! 28 | @method completePublicKeyPEM: 29 | @abstract Adds header and footer to incomplete PEM text. 30 | @discussion When storing a hard-coded PEM-encoded key in the application source, precautions are needed against easy replacement of the key. One way is to construct the key's PEM encoding step by step, appending each line to a mutable string until the base64-encoded part of the key is complete. You can then pass the base64-encoded key to this function and get complete PEM-encoded key as the result, with BEGIN and END lines added. 31 | @param partialPEM Base64-encoded part of the PEM key without BEGIN or END lines. 32 | @result An autoreleased string containing complete PEM-encoded DSA public key. 33 | */ 34 | + (NSString *)completePublicKeyPEM:(NSString *)partialPEM; 35 | 36 | /*! 37 | @method setPubKey: 38 | @abstract Sets DSA public key to the passed key in PEM format. 39 | @discussion Sets DSA public key in the verifier object to the argument which is a PEM-encoded DSA public key. 40 | @param pubKey PEM-encoded DSA public key. 41 | @result YES on success, NO on error (err may or may not be populated). 42 | */ 43 | - (BOOL)setPublicKey:(NSString *)pubKey error:(NSError **)err; 44 | 45 | /*! 46 | @method verify 47 | @abstract Verifies registration code in the regName property using public DSA key. 48 | @discussion Takes regName and regCode properties and verifies regCode against regName using public DSA certificate. 49 | @result YES if regCode is valid, NO if not. If an error was recovered it will be set in the err parameter 50 | */ 51 | - (BOOL)verifyRegCode:(NSString *)regCode forName:(NSString *)name error:(NSError **)err; 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /objc/CFobLicVerifier.m: -------------------------------------------------------------------------------- 1 | // 2 | // CFobLicVerifier.m 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 06/02/2009. 6 | // Follow me on Twitter @glebd. 7 | // Copyright 2009-2015 PixelEspresso. All rights reserved. 8 | // Licensed under BSD license. 9 | // 10 | 11 | #import "CFobLicVerifier.h" 12 | #import "CFobError.h" 13 | 14 | @interface CFobLicVerifier () 15 | @property (retain) __attribute__((NSObject)) SecKeyRef publicKey; 16 | @end 17 | 18 | 19 | @implementation CFobLicVerifier 20 | 21 | @synthesize blacklist = _blacklist; 22 | @synthesize publicKey = _publicKey; 23 | 24 | #pragma mark - 25 | #pragma mark Class methods 26 | 27 | + (NSString *)completePublicKeyPEM:(NSString *)partialPEM { 28 | NSString *dashes = @"-----"; 29 | NSString *begin = @"BEGIN"; 30 | NSString *end = @"END"; 31 | NSString *key = @"KEY"; 32 | NSString *public = @"DSA PUBLIC"; 33 | NSMutableString *pem = [NSMutableString string]; 34 | [pem appendString:dashes]; 35 | [pem appendString:begin]; 36 | [pem appendString:@" "]; 37 | [pem appendString:public]; 38 | [pem appendString:@" "]; 39 | [pem appendString:key]; 40 | [pem appendString:dashes]; 41 | [pem appendString:@"\n"]; 42 | [pem appendString:partialPEM]; 43 | [pem appendString:dashes]; 44 | [pem appendString:end]; 45 | [pem appendString:@" "]; 46 | [pem appendString:public]; 47 | [pem appendString:@" "]; 48 | [pem appendString:key]; 49 | [pem appendString:dashes]; 50 | [pem appendString:@"\n"]; 51 | return [NSString stringWithString:pem]; 52 | } 53 | 54 | #pragma mark - 55 | #pragma mark Lifecycle 56 | 57 | #if !__has_feature(objc_arc) 58 | - (void)finalize 59 | { 60 | self.publicKey = nil; 61 | [super finalize]; 62 | } 63 | 64 | - (void)dealloc 65 | { 66 | self.publicKey = nil; 67 | self.blacklist = nil; 68 | [super dealloc]; 69 | } 70 | #endif 71 | 72 | #pragma mark - 73 | #pragma mark API 74 | 75 | - (BOOL)setPublicKey:(NSString *)pubKey error:(NSError **)err 76 | { 77 | self.publicKey = nil; 78 | 79 | // Validate the argument. 80 | if (pubKey == nil || [pubKey length] < 1) { 81 | CFobAssignErrorWithDescriptionAndCode(err, @"Invalid key.", CFobErrorCodeInvalidKey); 82 | return NO; 83 | } 84 | 85 | SecItemImportExportKeyParameters params = {}; 86 | SecExternalItemType keyType = kSecItemTypePublicKey; 87 | SecExternalFormat keyFormat = kSecFormatPEMSequence; 88 | CFArrayRef importArray = NULL; 89 | 90 | NSData *pubKeyData = [pubKey dataUsingEncoding:NSUTF8StringEncoding]; 91 | #if __has_feature(objc_arc) 92 | CFDataRef pubKeyDataRef = (__bridge CFDataRef)pubKeyData; 93 | #else 94 | CFDataRef pubKeyDataRef = (CFDataRef)pubKeyData; 95 | #endif 96 | 97 | OSStatus importError = SecItemImport(pubKeyDataRef, NULL, &keyFormat, &keyType, 0, ¶ms, NULL, &importArray); 98 | 99 | if (importError) { 100 | CFobAssignErrorWithDescriptionAndCode(err, @"Unable to decode key.", CFobErrorCodeCouldNotDecode); 101 | if (importArray) { 102 | CFRelease(importArray); 103 | } 104 | return NO; 105 | } 106 | 107 | self.publicKey = (SecKeyRef)CFArrayGetValueAtIndex(importArray, 0); 108 | CFRelease(importArray); 109 | return YES; 110 | } 111 | 112 | - (BOOL)verifyRegCode:(NSString *)regCode forName:(NSString *)name error:(NSError **)err 113 | { 114 | if (name == nil || [name length] < 1) { 115 | CFobAssignErrorWithDescriptionAndCode(err, @"No name for the registration code.", CFobErrorCodeNoName); 116 | return NO; 117 | } 118 | 119 | if (!self.publicKey) { 120 | CFobAssignErrorWithDescriptionAndCode(err, @"Invalid key.", CFobErrorCodeInvalidKey); 121 | return NO; 122 | } 123 | 124 | // Replace 9s with Is and 8s with Os 125 | NSString *regKeyTemp = [regCode stringByReplacingOccurrencesOfString:@"9" withString:@"I"]; 126 | NSString *regKeyBase32 = [regKeyTemp stringByReplacingOccurrencesOfString:@"8" withString:@"O"]; 127 | // Remove dashes from the registration key if they are there (dashes are optional). 128 | NSString *keyNoDashes = [regKeyBase32 stringByReplacingOccurrencesOfString:@"-" withString:@""]; 129 | NSData *keyData = [keyNoDashes dataUsingEncoding:NSUTF8StringEncoding]; 130 | NSData *nameData = [name dataUsingEncoding:NSUTF8StringEncoding]; 131 | #if __has_feature(objc_arc) 132 | CFDataRef keyDataRef = (__bridge CFDataRef)keyData; 133 | CFDataRef nameDataRef = (__bridge CFDataRef)nameData; 134 | #else 135 | CFDataRef keyDataRef = (CFDataRef)keyData; 136 | CFDataRef nameDataRef = (CFDataRef)nameData; 137 | #endif 138 | 139 | // Note: A transform group is not used here because there appears to be a bug connecting the output of a decode transform to kSecSignatureAttributeName. Execution of the group randomly fails. 140 | 141 | BOOL result = NO; 142 | SecTransformRef decoder = SecDecodeTransformCreate(kSecBase32Encoding, NULL); 143 | if (decoder) { 144 | SecTransformSetAttribute(decoder, kSecTransformInputAttributeName, keyDataRef, NULL); 145 | CFDataRef signature = SecTransformExecute(decoder, NULL); 146 | if (signature) { 147 | 148 | // reverse the signature to check for truncated data / additional data entered by the user 149 | NSData *reversedKeyData = nil; 150 | SecTransformRef encoder = SecEncodeTransformCreate(kSecBase32Encoding, NULL); 151 | if (encoder) { 152 | SecTransformSetAttribute(encoder, kSecTransformInputAttributeName, signature, NULL); 153 | reversedKeyData = CFBridgingRelease(SecTransformExecute(encoder, NULL)); 154 | } 155 | CFRelease(encoder); 156 | 157 | if (!reversedKeyData) { 158 | return NO; 159 | } 160 | 161 | NSString *reversedKeyString = [[NSString alloc] initWithData:reversedKeyData encoding:NSUTF8StringEncoding]; 162 | 163 | // Cut off the padding. 164 | NSString *reversedKeyNoDashesNoPadding = [reversedKeyString stringByReplacingOccurrencesOfString:@"=" withString:@""]; 165 | 166 | if (![reversedKeyNoDashesNoPadding isEqualToString:keyNoDashes]) { 167 | return NO; 168 | } 169 | 170 | //now verify the key 171 | SecTransformRef verifier = SecVerifyTransformCreate(self.publicKey, signature, NULL); 172 | if (verifier) { 173 | SecTransformSetAttribute(verifier, kSecTransformInputAttributeName, nameDataRef, NULL); 174 | SecTransformSetAttribute(verifier, kSecDigestTypeAttribute, kSecDigestSHA1, NULL); 175 | CFErrorRef error; 176 | CFBooleanRef transformResult = SecTransformExecute(verifier, &error); 177 | if (transformResult) { 178 | result = (transformResult == kCFBooleanTrue); 179 | CFRelease(transformResult); 180 | } 181 | CFRelease(verifier); 182 | } 183 | CFRelease(signature); 184 | } 185 | CFRelease(decoder); 186 | } 187 | return result; 188 | } 189 | 190 | @end 191 | -------------------------------------------------------------------------------- /objc/CocoaFob-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | FMWK 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /objc/CocoaFobARC-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | FMWK 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /objc/CocoaFobTests/CocoaFobTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobTests.m 3 | // CocoaFobTests 4 | // 5 | // Twitter: @glebd 6 | // Website: http://pixelespressoapps.com 7 | // 8 | // License: BSD 9 | // Created by Gleb Dolgich on 05/07/2015. 10 | // 11 | // 12 | 13 | #import 14 | 15 | #import "CFobLicGenerator.h" 16 | #import "CFobLicVerifier.h" 17 | 18 | @interface CocoaFobTests : XCTestCase 19 | @property (nonatomic, strong) CFobLicGenerator *generator; 20 | @property (nonatomic, strong) CFobLicVerifier *verifier; 21 | @property (nonatomic, strong) NSString *pubKey; 22 | @end 23 | 24 | @implementation CocoaFobTests 25 | 26 | static NSString *privKey = 27 | @"-----BEGIN DSA PRIVATE KEY-----\n" 28 | "MIH5AgEAAkEA8wm04e0QcQRoAVJWWnUw/4rQEKbLKjujJu6oyEv7Y2oT3itY5pbO\n" 29 | "bgYCHEu9FBizqq7apsWYSF3YXiRjKlg10wIVALfs9eVL10PhoV6zczFpi3C7FzWN\n" 30 | "AkBaPhALEKlgIltHsumHdTSBqaVoR1/bmlgw/BCC13IAsW40nkFNsK1OVwjo2ocn\n" 31 | "3MwW4Rdq6uLm3DlENRZ5bYrTAkEA4reDYZKAl1vx+8EIMP/+2Z7ekydHfX0sTMDg\n" 32 | "kxhtRm6qtcywg01X847Y9ySgNepqleD+Ka2Wbucj1pOry8MoDQIVAIXgAB9GBLh4\n" 33 | "keUwLHBtpClnD5E8\n" 34 | "-----END DSA PRIVATE KEY-----\n"; 35 | 36 | static NSString *regName = @"decloner|Joe Bloggs"; 37 | 38 | - (void)setUp 39 | { 40 | [super setUp]; 41 | 42 | self.generator = [[CFobLicGenerator alloc] init]; 43 | self.verifier = [[CFobLicVerifier alloc] init]; 44 | 45 | // Modelled after AquaticPrime's method of splitting public key to obfuscate it. 46 | // It is probably better if you invent your own splitting pattern. Go wild. 47 | NSMutableString *pubKeyBase64 = [NSMutableString string]; 48 | [pubKeyBase64 appendString:@"MIHxMIGoBgcqhkj"]; 49 | [pubKeyBase64 appendString:@"OOAQBMIGcAkEA8wm04e0QcQRoAVJW"]; 50 | [pubKeyBase64 appendString:@"WnUw/4rQEKbLKjujJu6o\n"]; 51 | [pubKeyBase64 appendString:@"yE"]; 52 | [pubKeyBase64 appendString:@"v7Y2oT3itY5pbObgYCHEu9FBizqq7apsWYSF3YX"]; 53 | [pubKeyBase64 appendString:@"iRjKlg10wIVALfs9eVL10Ph\n"]; 54 | [pubKeyBase64 appendString:@"oV6zczFpi3C7FzWNAkBaPhALEKlgIltHsumHdTSBqaVoR1/bmlgw"]; 55 | [pubKeyBase64 appendString:@"/BCC13IAsW40\n"]; 56 | [pubKeyBase64 appendString:@"nkFNsK1OVwjo2ocn"]; 57 | [pubKeyBase64 appendString:@"3M"]; 58 | [pubKeyBase64 appendString:@"wW"]; 59 | [pubKeyBase64 appendString:@"4Rdq6uLm3DlENRZ5bYrTA"]; 60 | [pubKeyBase64 appendString:@"0QAAkEA4reDYZKAl1vx+8EI\n"]; 61 | [pubKeyBase64 appendString:@"MP/+"]; 62 | [pubKeyBase64 appendString:@"2Z7ekydHfX0sTMDgkxhtRm6qtcywg01X847Y9ySgNepqleD+Ka2Wbucj1pOr\n"]; 63 | [pubKeyBase64 appendString:@"y8MoDQ==\n"]; 64 | 65 | self.pubKey = [CFobLicVerifier completePublicKeyPEM:pubKeyBase64]; 66 | } 67 | 68 | - (void)tearDown { 69 | // Put teardown code here. This method is called after the invocation of each test method in the class. 70 | [super tearDown]; 71 | } 72 | 73 | - (void)testSetPrivateKey { 74 | NSError *error = nil; 75 | BOOL result = [self.generator setPrivateKey:privKey error:&error]; 76 | XCTAssertTrue(result, "Must be able to set private key in license generator"); 77 | } 78 | 79 | - (void)testSetPublicKey { 80 | NSError *error = nil; 81 | BOOL result = [self.verifier setPublicKey:self.pubKey error:&error]; 82 | XCTAssertTrue(result, "Must be able to set public key in license verifier"); 83 | } 84 | 85 | - (void)testGenerate { 86 | NSError *error = nil; 87 | BOOL result = [self.generator setPrivateKey:privKey error:&error]; 88 | XCTAssertTrue(result, "Must be able to set private key in license generator"); 89 | NSString *regCode = [self.generator generateRegCodeForName:regName error:&error]; 90 | XCTAssertNotNil(regCode, "Generated registration code must not be nil"); 91 | } 92 | 93 | - (void)testVerify { 94 | NSError *error = nil; 95 | BOOL result = [self.verifier setPublicKey:self.pubKey error:&error]; 96 | XCTAssertTrue(result, "Must be able to set public key in license verifier"); 97 | result = [self.verifier verifyRegCode:@"GAWQE-F9AQP-XJCCL-PAFAX-NU5XX-EUG6W-KLT3H-VTEB9-A9KHJ-8DZ5R-DL74G-TU4BN-7ATPY-3N4XB-V4V27-Q" forName:@"Joe Bloggs" error:&error]; 98 | XCTAssertTrue(result, "Must be able to verify pre-generated registration code"); 99 | } 100 | 101 | - (void)testGenerateAndVerify { 102 | NSError *error = nil; 103 | BOOL result = [self.generator setPrivateKey:privKey error:&error]; 104 | XCTAssertTrue(result, "Must be able to set private key in license generator"); 105 | NSString *regCode = [self.generator generateRegCodeForName:regName error:&error]; 106 | result = [self.verifier setPublicKey:self.pubKey error:&error]; 107 | XCTAssertTrue(result, "Must be able to set public key in license verifier"); 108 | result = [self.verifier verifyRegCode:regCode forName:regName error:&error]; 109 | XCTAssertTrue(result, "Must be able to generate and verify registration code"); 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /objc/CocoaFobTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /objc/MyApp.scriptSuite: -------------------------------------------------------------------------------- 1 | // Decloner.scriptSuite: 2 | { 3 | Name = Decloner; 4 | AppleEventCode = "DeCl"; 5 | Commands = { 6 | "GetURL" = { 7 | CommandClass = DCDeclonerCommand; 8 | AppleEventCode = GURL; 9 | AppleEventClassCode = GURL; 10 | }; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /objc/MyApp.scriptTerminology: -------------------------------------------------------------------------------- 1 | // Decloner.scriptTerminology: 2 | { 3 | Name = "Decloner commands"; 4 | Description = "Commands to handle a URL"; 5 | Commands = { 6 | "GetURL" = { 7 | "Name" = "get URL"; 8 | "Description" = "Open a URL"; 9 | }; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /objc/NSData+PECrypt.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+PECrypt.h 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 09/02/2009. 6 | // Follow me on Twitter @gbd 7 | // Copyright (C) 2009 PixelEspresso. All rights reserved. 8 | // Licensed under CC Attribution License 3.0 9 | // 10 | 11 | #import 12 | 13 | 14 | @interface NSData (PECrypt) 15 | 16 | - (NSString *)base32; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /objc/NSData+PECrypt.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+PECrypt.m 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 09/02/2009. 6 | // Follow me on Twitter @gbd 7 | // Copyright (C) 2009 PixelEspresso. All rights reserved. 8 | // Licensed under CC Attribution License 3.0 9 | // 10 | 11 | #import "NSData+PECrypt.h" 12 | #import "encoder.h" 13 | 14 | 15 | @implementation NSData (PECrypt) 16 | 17 | - (NSString *)base32 { 18 | if (![self length]) 19 | return @""; 20 | size_t bufsize = base32_encoder_buffer_size([self length]); 21 | char *buf = malloc(bufsize+1); 22 | buf[bufsize] = 0; 23 | if (!buf) 24 | return @""; 25 | base32_encode((uint8_t *)buf, bufsize, [self bytes], [self length]); 26 | NSString *s = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding]; 27 | free(buf); 28 | return s; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /objc/NSString+PECrypt.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+PECrypt.h 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 09/02/2009. 6 | // Follow me on Twitter @gbd 7 | // Copyright (C) 2009 PixelEspresso. All rights reserved. 8 | // Licensed under CC Attribution License 3.0 9 | // 10 | // Base64 functions based on Dave Dribin's code: 11 | // http://www.dribin.org/dave/blog/archives/2006/03/12/base64_cocoa/ 12 | // 13 | 14 | #import 15 | 16 | @interface NSString (PXCrypt) 17 | - (NSData *)sha1; 18 | - (NSString *)base64DecodeWithBreaks:(BOOL)lineBreaks; 19 | - (NSString *)base64Decode; 20 | @end 21 | -------------------------------------------------------------------------------- /objc/NSString+PECrypt.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+PECrypt.m 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 09/02/2009. 6 | // Follow me on Twitter @gbd 7 | // Copyright (C) 2009 PixelEspresso. All rights reserved. 8 | // Licensed under CC Attribution License 3.0 9 | // 10 | 11 | #import "NSString+PECrypt.h" 12 | #include 13 | 14 | @implementation NSString (PXCrypt) 15 | 16 | - (NSData *)sha1 { 17 | unsigned char digest[CC_SHA1_DIGEST_LENGTH]; 18 | NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; 19 | CC_SHA1([data bytes], (unsigned int)[data length], digest); 20 | return [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; 21 | } 22 | 23 | // Formely based on Dave Dribin's code, http://www.dribin.org/dave/blog/archives/2006/03/12/base64_cocoa/ 24 | // Updated to use CommonCrypto instead of OpenSSL 25 | - (NSString *)base64DecodeWithBreaks:(BOOL)lineBreaks { 26 | SecTransformRef transform = SecDecodeTransformCreate(kSecBase64Encoding, NULL); 27 | NSData *output = nil; 28 | if (SecTransformSetAttribute(transform, kSecTransformInputAttributeName, [self dataUsingEncoding:NSASCIIStringEncoding], NULL)) { 29 | output = (NSData *)SecTransformExecute(transform, NULL); 30 | } 31 | CFRelease(transform); 32 | NSString *decoded = [NSString stringWithUTF8String:[output bytes]]; 33 | [output release]; 34 | return decoded; 35 | } 36 | 37 | - (NSString *)base64Decode { 38 | return [self base64DecodeWithBreaks:NO]; 39 | } 40 | 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /objc/NSString-Base64Extensions.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2006 Dave Dribin (http://www.dribin.org/dave/) 2 | // Permission is hereby granted, free of charge, to any person obtaining 3 | // a copy of this software and associated documentation files (the 4 | // "Software"), to deal in the Software without restriction, including 5 | // without limitation the rights to use, copy, modify, merge, publish, 6 | // distribute, sublicense, and/or sell copies of the Software, and to 7 | // permit persons to whom the Software is furnished to do so, subject to 8 | // the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be 11 | // included in all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 17 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | #import 22 | 23 | 24 | @interface NSString (Base64) 25 | 26 | - (NSData *) decodeBase64; 27 | - (NSData *) decodeBase64WithNewlines: (BOOL) encodedWithNewlines; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /objc/NSString-Base64Extensions.m: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2006 Dave Dribin (http://www.dribin.org/dave/) 2 | // Permission is hereby granted, free of charge, to any person obtaining 3 | // a copy of this software and associated documentation files (the 4 | // "Software"), to deal in the Software without restriction, including 5 | // without limitation the rights to use, copy, modify, merge, publish, 6 | // distribute, sublicense, and/or sell copies of the Software, and to 7 | // permit persons to whom the Software is furnished to do so, subject to 8 | // the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be 11 | // included in all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 17 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | // 21 | // Modified by Gleb Dolgich @gbd 22 | // PixelEspresso, http://www.pixelespressoapps.com/ 23 | 24 | #import "NSString-Base64Extensions.h" 25 | #import 26 | 27 | @implementation NSString (Base64) 28 | 29 | - (NSData *) decodeBase64; 30 | { 31 | return [self decodeBase64WithNewlines: YES]; 32 | } 33 | 34 | - (NSData *)decodeBase64WithNewlines:(BOOL)encodedWithNewlines; 35 | { 36 | SecTransformRef transform = SecDecodeTransformCreate(kSecBase64Encoding, NULL); 37 | NSData *output = nil; 38 | if (SecTransformSetAttribute(transform, kSecTransformInputAttributeName, [self dataUsingEncoding:NSASCIIStringEncoding], NULL)) { 39 | output = (NSData *)SecTransformExecute(transform, NULL); 40 | } 41 | [output autorelease]; 42 | CFRelease(transform); 43 | return output; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /objc/URLCommand.h: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFob 3 | // 4 | // URLCommand.h 5 | // 6 | // Support for custom URL scheme for app registration 7 | // 8 | // Created by Gleb Dolgich on 20/03/2009. 9 | // Follow me on Twitter @gbd 10 | // Copyright (C) 2009 PixelEspresso. All rights reserved. 11 | // Licensed under CC Attribution License 3.0 12 | // 13 | // Based on "Handling URL schemes in Cocoa", a blog post by Kimbro Staken 14 | // 15 | // 16 | 17 | #import 18 | 19 | @interface URLCommand : NSScriptCommand { 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /objc/URLCommand.m: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFob 3 | // 4 | // URLCommand.h 5 | // 6 | // Support for custom URL scheme for app registration. 7 | // Pay attention to the TODO: comments below. 8 | // 9 | // Created by Gleb Dolgich on 20/03/2009. 10 | // Follow me on Twitter @glebd 11 | // Copyright (C) 2009 PixelEspresso. All rights reserved. 12 | // Licensed under CC Attribution License 3.0 13 | // 14 | // Based on "Handling URL schemes in Cocoa", a blog post by Kimbro Staken 15 | // 16 | // 17 | 18 | #import "URLCommand.h" 19 | #import "NSString+PECrypt.h" 20 | 21 | @interface URLCommand () 22 | 23 | - (id)performWithURL:(NSString *)url; 24 | 25 | @end 26 | 27 | 28 | @implementation URLCommand 29 | 30 | - (id)performDefaultImplementation { 31 | NSString *url = [self directParameter]; 32 | NSLog(@"URL = %@", url); 33 | return [self performWithURL:url]; 34 | } 35 | 36 | - (id)performWithURL:(NSString *)url { 37 | // URL has the following format: 38 | // com.mycompany.myapp.lic:/// 39 | NSArray *protocolAndTheRest = [url componentsSeparatedByString:@"://"]; 40 | if ([protocolAndTheRest count] != 2) { 41 | NSLog(@"License URL is invalid (no protocol)"); 42 | return nil; 43 | } 44 | // Separate user name and serial number 45 | NSArray *userNameAndSerialNumber = [[protocolAndTheRest objectAtIndex:1] componentsSeparatedByString:@"/"]; 46 | if ([userNameAndSerialNumber count] != 2) { 47 | NSLog(@"License URL is invalid (missing parts)"); 48 | return nil; 49 | } 50 | // Decode base64-encoded user name 51 | NSString *usernameb64 = (NSString *)[userNameAndSerialNumber objectAtIndex:0]; 52 | NSString *username = [usernameb64 base64Decode]; 53 | NSLog(@"User name: %@", username); 54 | NSString *serial = (NSString *)[userNameAndSerialNumber objectAtIndex:1]; 55 | NSLog(@"Serial: %@", serial); 56 | // TODO: Save registration to preferences. 57 | // TODO: Broadcast notification of a changed registration information. 58 | return nil; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /objc/cocoafob.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 28/02/2009 \" DATE 7 | .Dt cocoafob 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm cocoafob, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner -------------------------------------------------------------------------------- /objc/cocoafob.m: -------------------------------------------------------------------------------- 1 | // 2 | // cocoafob.m 3 | // CocoaFob Test Utility 4 | // 5 | // Created by Gleb Dolgich on 09/02/2009. 6 | // Follow me on Twitter @glebd. 7 | // Copyright (C) 2009-2012 PixelEspresso. All rights reserved. 8 | // BSD License 9 | // 10 | 11 | #import 12 | 13 | #import 14 | #import 15 | 16 | // Pass private key file name and registration name string to generate an autoreleased string containing registration code. 17 | NSString *codegen(NSString *privKeyFileName, NSString *regName) 18 | { 19 | NSError *err = nil; 20 | NSString *privKey = [NSString stringWithContentsOfFile:privKeyFileName encoding:NSASCIIStringEncoding error:&err]; 21 | if (privKey == nil) 22 | return nil; 23 | 24 | CFobLicGenerator *generator = [[[CFobLicGenerator alloc] init] autorelease]; 25 | if (![generator setPrivateKey:privKey error:&err]) { 26 | NSLog(@"%@", err); 27 | return nil; 28 | } 29 | 30 | NSString *regCode = [generator generateRegCodeForName:regName error:&err]; 31 | if (regCode == nil) { 32 | NSLog(@"%@", err); 33 | return nil; 34 | } 35 | 36 | return regCode; 37 | } 38 | 39 | // Pass public key, registration name and registration code to verify it 40 | BOOL codecheck(NSString *pubKeyFileName, NSString *regName, NSString *regCode) 41 | { 42 | NSError *err = nil; 43 | NSString *pubKey = [NSString stringWithContentsOfFile:pubKeyFileName encoding:NSASCIIStringEncoding error:&err]; 44 | if (pubKey == nil) 45 | return NO; 46 | 47 | CFobLicVerifier *verifier = [[[CFobLicVerifier alloc] init] autorelease]; 48 | if (![verifier setPublicKey:pubKey error:&err]) { 49 | NSLog(@"%@", err); 50 | return NO; 51 | } 52 | 53 | BOOL result = [verifier verifyRegCode:regCode forName:regName error:&err]; 54 | if (!result) 55 | NSLog(@"%@", err); 56 | 57 | return result; 58 | } 59 | 60 | // Uses NSUserDefaults to parse command-line arguments: 61 | // -privkey 62 | // -name 63 | // Prints generated registration code. 64 | // -pubkey 65 | // -code 66 | // Verifies registration code. 67 | int main(int argc, const char * argv[]) 68 | { 69 | NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 70 | 71 | puts("CocoaFob Command Line Utility Version 2.0"); 72 | 73 | #ifdef TEST 74 | smoketest(); 75 | #endif 76 | NSUserDefaults *args = [NSUserDefaults standardUserDefaults]; 77 | NSString *privKeyFileName = [args stringForKey:@"privkey"]; 78 | NSString *pubKeyFileName = [args stringForKey:@"pubkey"]; 79 | NSString *regName = [args stringForKey:@"name"]; 80 | NSString *regCodeIn = [args stringForKey:@"code"]; 81 | if (!((privKeyFileName && regName) || (pubKeyFileName && regName && regCodeIn))) { 82 | puts("Usage: cocoafob {-privkey -name |-pubkey -name -code }"); 83 | return 1; 84 | } 85 | int retval = 0; 86 | if (regCodeIn && pubKeyFileName && regName) { 87 | // Verify supplied registration code 88 | BOOL check = codecheck(pubKeyFileName, regName, regCodeIn); 89 | if (check) { 90 | puts("OK"); 91 | } else { 92 | puts("Error"); 93 | retval = 3; 94 | } 95 | } else { 96 | // Generate registration code 97 | NSString *regCode = codegen(privKeyFileName, regName); 98 | if (!regCode) { 99 | puts("Error"); 100 | retval = 2; 101 | } else { 102 | puts([regCode UTF8String]); 103 | } 104 | } 105 | [pool drain]; 106 | return retval; 107 | } 108 | -------------------------------------------------------------------------------- /objc/cocoafob.xcodeproj/TemplateIcon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glebd/cocoafob/7903c431e4838861afb29b8b6f51d445fa552072/objc/cocoafob.xcodeproj/TemplateIcon.icns -------------------------------------------------------------------------------- /objc/cocoafob.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /objc/decoder.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2007 Samuel Tesla 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | * THE SOFTWARE. 20 | */ 21 | 22 | #include "decoder.h" 23 | 24 | static inline uint8_t 25 | decode_bits (const uint8_t bits) 26 | { 27 | uint8_t table[] = { 28 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 29 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 30 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 31 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 32 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 33 | 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 34 | 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 35 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 36 | 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 37 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 38 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 39 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 40 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 41 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 42 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 43 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 44 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 45 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 46 | 0xFF, 0xFF, 0xFF, 0xFF 47 | }; 48 | return table[bits]; 49 | } 50 | 51 | size_t 52 | base32_decoder_buffer_size (const size_t encodedTextLength) 53 | { 54 | if (encodedTextLength == 0 || encodedTextLength % 8 != 0) 55 | return 0; 56 | return encodedTextLength * 8 / 5; 57 | } 58 | 59 | size_t 60 | base32_decode (uint8_t *output, const size_t outputLength, const uint8_t *input, const size_t inputLength) 61 | { 62 | if (outputLength == 0 || inputLength == 0 || inputLength % 8 != 0) 63 | return 0; 64 | 65 | size_t bytes = 0; 66 | uint8_t currentByte = 0; 67 | unsigned offset; 68 | for (offset = 0; offset < inputLength && bytes < outputLength; offset += 8) 69 | { 70 | output[bytes] = decode_bits (input[offset + 0]) << 3; 71 | currentByte = decode_bits (input[offset + 1]); 72 | output[bytes] += currentByte >> 2; 73 | output[bytes + 1] = (currentByte & 0x03) << 6; 74 | 75 | if (input[offset + 2] == '=') 76 | return bytes + 1; 77 | else 78 | bytes++; 79 | 80 | output[bytes] += decode_bits (input[offset + 2]) << 1; 81 | currentByte = decode_bits (input[offset + 3]); 82 | output[bytes] += currentByte >> 4; 83 | output[bytes + 1] = currentByte << 4; 84 | 85 | if (input[offset + 4] == '=') 86 | return bytes + 1; 87 | else 88 | bytes++; 89 | 90 | currentByte = decode_bits (input[offset + 4]); 91 | output[bytes] += currentByte >> 1; 92 | output[bytes + 1] = currentByte << 7; 93 | 94 | if (input[offset + 5] == '=') 95 | return bytes + 1; 96 | else 97 | bytes++; 98 | 99 | output[bytes] += decode_bits (input[offset + 5]) << 2; 100 | currentByte = decode_bits (input[offset + 6]); 101 | output[bytes] += currentByte >> 3; 102 | output[bytes + 1] = (currentByte & 0x07) << 5; 103 | 104 | if (input[offset + 7] == '=') 105 | return bytes + 1; 106 | else 107 | bytes++; 108 | 109 | output[bytes] += decode_bits (input[offset + 7]) & 0x1F; 110 | bytes++; 111 | } 112 | return bytes; 113 | } 114 | -------------------------------------------------------------------------------- /objc/decoder.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2007 Samuel Tesla 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | * THE SOFTWARE. 20 | */ 21 | 22 | #ifndef DECODER_H 23 | #define DECODER_H 24 | 25 | #include 26 | #include 27 | 28 | size_t base32_decoder_buffer_size (const size_t encodedTextLength); 29 | 30 | size_t base32_decode (uint8_t *output, const size_t outputLength, 31 | const uint8_t *input, const size_t inputLength); 32 | #endif 33 | -------------------------------------------------------------------------------- /objc/encoder.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2007 Samuel Tesla 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | * THE SOFTWARE. 20 | */ 21 | 22 | #include "encoder.h" 23 | 24 | size_t 25 | base32_encoder_last_quintent (const size_t bytes) 26 | { 27 | size_t quintets = bytes * 8 / 5; 28 | size_t remainder = bytes % 5; 29 | return remainder == 0 ? quintets : quintets + 1; 30 | } 31 | 32 | size_t 33 | base32_encoder_output_padding_size (const size_t bytes) 34 | { 35 | unsigned remainder = bytes % 5; 36 | return remainder == 0 ? 0 : (5 - remainder) * 8 / 5; 37 | } 38 | 39 | size_t 40 | base32_encoder_buffer_size (const size_t bytes) 41 | { 42 | return base32_encoder_last_quintent (bytes) + 43 | base32_encoder_output_padding_size (bytes); 44 | } 45 | 46 | static size_t 47 | base32_encoder_encode_bits (size_t position, const uint8_t *buffer) 48 | { 49 | size_t offset = position / 8 * 5; 50 | switch (position % 8) 51 | { 52 | case 0: 53 | return 54 | ((buffer[offset] & 0xF8) >> 3); 55 | 56 | case 1: 57 | return 58 | ((buffer[offset] & 0x07) << 2) + 59 | ((buffer[offset + 1] & 0xC0) >> 6); 60 | 61 | case 2: 62 | return 63 | ((buffer[offset + 1] & 0x3E) >> 1); 64 | 65 | case 3: 66 | return 67 | ((buffer[offset + 1] & 0x01) << 4) + 68 | ((buffer[offset + 2] & 0xF0) >> 4); 69 | 70 | case 4: 71 | return 72 | ((buffer[offset + 2] & 0x0F) << 1) + 73 | ((buffer[offset + 3] & 0x80) >> 7); 74 | 75 | case 5: 76 | return 77 | ((buffer[offset + 3] & 0x7C) >> 2); 78 | 79 | case 6: 80 | return 81 | ((buffer[offset + 3] & 0x03) << 3) + 82 | ((buffer[offset + 4] & 0xE0) >> 5); 83 | 84 | case 7: 85 | return 86 | buffer[offset + 4] & 0x1F; 87 | 88 | default: 89 | return 0; 90 | } 91 | } 92 | 93 | static inline uint8_t 94 | base32_encoder_encode_at_position (size_t position, const uint8_t *buffer) 95 | { 96 | const char *table = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 97 | size_t index = base32_encoder_encode_bits (position, buffer); 98 | return table[index]; 99 | } 100 | 101 | void 102 | base32_encode (uint8_t *output, const size_t outputLength, 103 | const uint8_t *input, const size_t inputLength) 104 | { 105 | size_t i; 106 | size_t quintets = base32_encoder_last_quintent(inputLength); 107 | for (i = 0; i < quintets; i++) 108 | output[i] = base32_encoder_encode_at_position (i, input); 109 | for (i = quintets; i < outputLength; i++) 110 | output[i] = '='; 111 | } 112 | -------------------------------------------------------------------------------- /objc/encoder.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2007 Samuel Tesla 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to deal 5 | * in the Software without restriction, including without limitation the rights 6 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | * copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | * THE SOFTWARE. 20 | */ 21 | 22 | #ifndef ENCODER_H 23 | #define ENCODER_H 24 | 25 | #include 26 | #include 27 | 28 | size_t base32_encoder_buffer_size (const size_t bytes); 29 | 30 | void base32_encode (uint8_t *output, const size_t outputLength, 31 | const uint8_t *input, const size_t inputLength); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /objc/pxlic.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "PxLicGenerator.h" 3 | #import "PxLicVerifier.h" 4 | 5 | void test() { 6 | NSString *privKey = @"-----BEGIN DSA PRIVATE KEY-----\nMIH4AgEAAkEApuYqEoIJcs8reEItoKJusdZEaeCU71pSUU8BjVXK/K4J5/tUnmmk\nS7JkhUjK7CP882sI1kTHEXaxIjAXVX74nQIVAL45/aSf+Mn2zhlPojZnjRchrQFV\nAkBoFzzLEHVaPa3qxMNXE27jGtqyZK/n3uTTa0eBflGPE4STA85JNVUFlsYrbxVh\nwy9QdfmGERgJupg+cxxIVmGRAkEAk8ZRoFZ5otEG+bfkkufh43/lYSLPz4dazRvp\naRWcbaatbNb6ojmAiXMdVHQSRgq+bHpTrA8CDAvyw/UeDpYO9gIUKXizaRlSWbB0\ngkb+zRrNLgk/XqM=\n-----END DSA PRIVATE KEY-----\n"; 7 | NSString *regName = @"product,User Name"; 8 | PxLicGenerator *generator = [PxLicGenerator generatorWithPrivateKey:privKey]; 9 | generator.regName = regName; 10 | [generator generate]; 11 | 12 | NSMutableString *pubKey = [NSMutableString string]; 13 | [pubKey appendString:@"MIHxMIGoBgcqhkjOOAQBMIGcAkEApuYqEoIJcs8reEItoKJusdZEaeCU71pSUU8B\n"]; 14 | [pubKey appendString:@"jVXK/K4J5/tUnmmkS7JkhUjK7CP882sI1kTHEXaxIjAXVX74nQIVAL45/aSf+Mn2\n"]; 15 | [pubKey appendString:@"zhlPojZnjRchrQFVAkBoFzzLEHVaPa3qxMNXE27jGtqyZK/n3uTTa0eBflGPE4ST\n"]; 16 | [pubKey appendString:@"A85JNVUFlsYrbxVhwy9QdfmGERgJupg+cxxIVmGRA0QAAkEAk8ZRoFZ5otEG+bfk\n"]; 17 | [pubKey appendString:@"kufh43/lYSLPz4dazRvpaRWcbaatbNb6ojmAiXMdVHQSRgq+bHpTrA8CDAvyw/Ue\n"]; 18 | [pubKey appendString:@"DpYO9g==\n"]; 19 | NSString *pem = [PxLicVerifier completePublicKeyPEM:pubKey]; 20 | PxLicVerifier *verifier = [PxLicVerifier verifierWithPublicKey:pem]; 21 | verifier.regName = regName; 22 | //verifier.regKey = @"GAWAE-FD6V7-7CQ8M-UJ5X2-8CPHW-L5BN8-NRLZY-DMXAC-CQEH3-SMF6C-AYTJK-6LT9U-8DAAN-EG9WW-592Y"; 23 | verifier.regKey = generator.regKey; 24 | if ([verifier verify]) 25 | printf("PASS"); 26 | else 27 | printf("FAIL"); 28 | } 29 | 30 | int main (int argc, const char * argv[]) { 31 | NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 32 | 33 | test(); 34 | 35 | [pool drain]; 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /php/License/base32.php: -------------------------------------------------------------------------------- 1 | '00000', 44 | 0x62 => '00001', 45 | 0x63 => '00010', 46 | 0x64 => '00011', 47 | 0x65 => '00100', 48 | 0x66 => '00101', 49 | 0x67 => '00110', 50 | 0x68 => '00111', 51 | 0x69 => '01000', 52 | 0x6a => '01001', 53 | 0x6b => '01010', 54 | 0x6c => '01011', 55 | 0x6d => '01100', 56 | 0x6e => '01101', 57 | 0x6f => '01110', 58 | 0x70 => '01111', 59 | 0x71 => '10000', 60 | 0x72 => '10001', 61 | 0x73 => '10010', 62 | 0x74 => '10011', 63 | 0x75 => '10100', 64 | 0x76 => '10101', 65 | 0x77 => '10110', 66 | 0x78 => '10111', 67 | 0x79 => '11000', 68 | 0x7a => '11001', 69 | 0x32 => '11010', 70 | 0x33 => '11011', 71 | 0x34 => '11100', 72 | 0x35 => '11101', 73 | 0x36 => '11110', 74 | 0x37 => '11111', 75 | ); 76 | 77 | /* Step 1 */ 78 | $inputCheck = strlen($inString) % 8; 79 | if(($inputCheck == 1)||($inputCheck == 3)||($inputCheck == 6)) { 80 | trigger_error('input to Base32Decode was a bad mod length: '.$inputCheck); 81 | return false; 82 | //return $this->raiseError('input to Base32Decode was a bad mod length: '.$inputCheck, null, 83 | // PEAR_ERROR_DIE, null, null, 'Net_RACE_Error', false ); 84 | } 85 | 86 | /* $deCompBits is a string that represents the bits as 0 and 1.*/ 87 | for ($i = 0; $i < strlen($inString); $i++) { 88 | $inChar = ord(substr($inString,$i,1)); 89 | if(isset($BASE32_TABLE[$inChar])) { 90 | $deCompBits .= $BASE32_TABLE[$inChar]; 91 | } else { 92 | trigger_error('input to Base32Decode had a bad character: '.$inChar.":".substr($inString,$i,1)); 93 | return false; 94 | //return $this->raiseError('input to Base32Decode had a bad character: '.$inChar, null, 95 | // PEAR_ERROR_DIE, null, null, 'Net_RACE_Error', false ); 96 | } 97 | } 98 | 99 | /* Step 5 */ 100 | $padding = strlen($deCompBits) % 8; 101 | $paddingContent = substr($deCompBits, (strlen($deCompBits) - $padding)); 102 | if(substr_count($paddingContent, '1')>0) { 103 | trigger_error('found non-zero padding in Base32Decode'); 104 | return false; 105 | //return $this->raiseError('found non-zero padding in Base32Decode', null, 106 | // PEAR_ERROR_DIE, null, null, 'Net_RACE_Error', false ); 107 | } 108 | 109 | /* Break the decompressed string into octets for returning */ 110 | $deArr = array(); 111 | for($i = 0; $i < (int)(strlen($deCompBits) / 8); $i++) { 112 | $deArr[$i] = chr(bindec(substr($deCompBits, $i*8, 8))); 113 | } 114 | 115 | $outString = join('',$deArr); 116 | 117 | return $outString; 118 | } 119 | 120 | ?> -------------------------------------------------------------------------------- /php/License/dsa_priv.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN DSA PRIVATE KEY----- 2 | MIH4AgEAAkEA5P9B2i+I6OAhGq9vH7xqdVeOYYHlS33GcIjLQlqurwNayQHa0fXM 3 | LLR0MEZljjiXq3cfJDrhHHDZdjsWm9KE5wIVAPWa2+7ZOUsA9S1FRIj+mzIPzwPD 4 | AkEAgc0H1l7vhB6euIEh0AisnfHsPtKqAhmqgMkf5qh1+QV8AH/tgt2OmGxfCU4j 5 | J4I6SSfSPj8Y2sZ8L18hV1rAMAJAIaxbtIbsR4bo5FVOqzTPu0YPJu8vYef/4aqc 6 | 5OsmLa9wa9LbZdN5IjsSzXaEgUG+rOxRH/KRhcL8dSO28ZrTsgIUQNQt9O/9KxdT 7 | iEmx/5yYySLhYHQ= 8 | -----END DSA PRIVATE KEY----- 9 | -------------------------------------------------------------------------------- /php/License/dsa_pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIHxMIGpBgcqhkjOOAQBMIGdAkEA5P9B2i+I6OAhGq9vH7xqdVeOYYHlS33GcIjL 3 | QlqurwNayQHa0fXMLLR0MEZljjiXq3cfJDrhHHDZdjsWm9KE5wIVAPWa2+7ZOUsA 4 | 9S1FRIj+mzIPzwPDAkEAgc0H1l7vhB6euIEh0AisnfHsPtKqAhmqgMkf5qh1+QV8 5 | AH/tgt2OmGxfCU4jJ4I6SSfSPj8Y2sZ8L18hV1rAMANDAAJAIaxbtIbsR4bo5FVO 6 | qzTPu0YPJu8vYef/4aqc5OsmLa9wa9LbZdN5IjsSzXaEgUG+rOxRH/KRhcL8dSO2 7 | 8ZrTsg== 8 | -----END PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /php/License/generateLicense.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
" id="pCode" />
5 |
6 |
" id="name" />
7 |
8 |
" id="email" />
9 |
10 |
11 |
12 | 13 | make_license($_POST["pCode"], $_POST["name"], $_POST["email"]); 19 | echo 'Your License:
' . $code .""; 20 | } 21 | ?> 22 | 23 | -------------------------------------------------------------------------------- /php/License/license_generator.php: -------------------------------------------------------------------------------- 1 | private_key = file_get_contents('./dsa_priv.pem', FILE_USE_INCLUDE_PATH); 18 | //read the public Key from disk 19 | $this->public_key = file_get_contents('./dsa_pub.pem', FILE_USE_INCLUDE_PATH); 20 | }#-#constructor() 21 | 22 | #-############################################# 23 | # desc: Create a license 24 | public function make_license($product_code, $name, $email) 25 | { 26 | ## NOTE ############################################### 27 | # If you change the parameters the function acepts do not 28 | # forget to change the lower string concatenation 29 | # to include all fields in the license generation 30 | 31 | $stringData = $product_code.",".$name.",".$email; 32 | 33 | ################################################# 34 | $binary_signature =""; 35 | openssl_sign($stringData, $binary_signature, $this->private_key, OPENSSL_ALGO_SHA1); 36 | // base 32 encode the signature 37 | $encoded = base32_encode($binary_signature); 38 | // replace O with 8 and I with 9 39 | $replacement = str_replace("O", "8", str_replace("I", "9", $encoded)); 40 | //remove padding if any. 41 | $padding = trim(str_replace("=", "", $replacement)); 42 | $dashed = rtrim(chunk_split($padding, 5,"-")); 43 | $theKey = substr($dashed, 0 , strlen($dashed) -1); 44 | 45 | return $theKey; 46 | } 47 | 48 | #-############################################# 49 | # desc: Verify License 50 | public function verify_license($product_code, $name, $email, $license) 51 | { 52 | ## NOTE ############################################### 53 | # If you change the parameters the function acepts do not 54 | # forget to change the lower string concatenation 55 | # to include all fields in the license generation 56 | 57 | $stringData = $product_code.",".$name.",".$email; 58 | 59 | ################################################# 60 | // replace O with 8 and I with 9 61 | $replacement = str_replace("8", "O", str_replace("9", "I", $license)); 62 | //remove Dashes. 63 | $undashed = trim(str_replace("-", "", $replacement)); 64 | // Pad the output length to a multiple of 8 with '=' characters 65 | $desiredLength = strlen($undashed); 66 | if($desiredLength % 8 != 0) { 67 | $desiredLength += (8 - ($desiredLength % 8)); 68 | $undashed = str_pad($undashed, $desiredLength, "="); 69 | } 70 | // decode Key 71 | $decodedHash = base32_decode($undashed); 72 | 73 | $ok = openssl_verify($stringData, $decodedHash, $this->public_key, OPENSSL_ALGO_SHA1); 74 | if ($ok == 1) { 75 | return TRUE; 76 | } elseif ($ok == 0) { 77 | return FALSE; 78 | } else { 79 | return FALSE; 80 | } 81 | } 82 | 83 | } # Class License 84 | ?> 85 | -------------------------------------------------------------------------------- /php/License/verifylicense.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
" id="pCode" />
5 |
6 |
" id="name" />
7 |
8 |
" id="email" />
9 |
10 |
11 |
12 |
13 |
14 | 15 | verify_license($_POST["pCode"], $_POST["name"], $_POST["email"],$_POST["key"]); 20 | if ($result) 21 | echo "Valid"; 22 | else 23 | echo "Invalid"; 24 | } 25 | ?> 26 | -------------------------------------------------------------------------------- /php/base32.php: -------------------------------------------------------------------------------- 1 | '00000', 44 | 0x62 => '00001', 45 | 0x63 => '00010', 46 | 0x64 => '00011', 47 | 0x65 => '00100', 48 | 0x66 => '00101', 49 | 0x67 => '00110', 50 | 0x68 => '00111', 51 | 0x69 => '01000', 52 | 0x6a => '01001', 53 | 0x6b => '01010', 54 | 0x6c => '01011', 55 | 0x6d => '01100', 56 | 0x6e => '01101', 57 | 0x6f => '01110', 58 | 0x70 => '01111', 59 | 0x71 => '10000', 60 | 0x72 => '10001', 61 | 0x73 => '10010', 62 | 0x74 => '10011', 63 | 0x75 => '10100', 64 | 0x76 => '10101', 65 | 0x77 => '10110', 66 | 0x78 => '10111', 67 | 0x79 => '11000', 68 | 0x7a => '11001', 69 | 0x32 => '11010', 70 | 0x33 => '11011', 71 | 0x34 => '11100', 72 | 0x35 => '11101', 73 | 0x36 => '11110', 74 | 0x37 => '11111', 75 | ); 76 | 77 | /* Step 1 */ 78 | $inputCheck = strlen($inString) % 8; 79 | if(($inputCheck == 1)||($inputCheck == 3)||($inputCheck == 6)) { 80 | trigger_error('input to Base32Decode was a bad mod length: '.$inputCheck); 81 | return false; 82 | //return $this->raiseError('input to Base32Decode was a bad mod length: '.$inputCheck, null, 83 | // PEAR_ERROR_DIE, null, null, 'Net_RACE_Error', false ); 84 | } 85 | 86 | /* $deCompBits is a string that represents the bits as 0 and 1.*/ 87 | for ($i = 0; $i < strlen($inString); $i++) { 88 | $inChar = ord(substr($inString,$i,1)); 89 | if(isset($BASE32_TABLE[$inChar])) { 90 | $deCompBits .= $BASE32_TABLE[$inChar]; 91 | } else { 92 | trigger_error('input to Base32Decode had a bad character: '.$inChar.":".substr($inString,$i,1)); 93 | return false; 94 | //return $this->raiseError('input to Base32Decode had a bad character: '.$inChar, null, 95 | // PEAR_ERROR_DIE, null, null, 'Net_RACE_Error', false ); 96 | } 97 | } 98 | 99 | /* Step 5 */ 100 | $padding = strlen($deCompBits) % 8; 101 | $paddingContent = substr($deCompBits, (strlen($deCompBits) - $padding)); 102 | if(substr_count($paddingContent, '1')>0) { 103 | trigger_error('found non-zero padding in Base32Decode'); 104 | return false; 105 | //return $this->raiseError('found non-zero padding in Base32Decode', null, 106 | // PEAR_ERROR_DIE, null, null, 'Net_RACE_Error', false ); 107 | } 108 | 109 | /* Break the decompressed string into octets for returning */ 110 | $deArr = array(); 111 | for($i = 0; $i < (int)(strlen($deCompBits) / 8); $i++) { 112 | $deArr[$i] = chr(bindec(substr($deCompBits, $i*8, 8))); 113 | } 114 | 115 | $outString = join('',$deArr); 116 | 117 | return $outString; 118 | } 119 | 120 | ?> -------------------------------------------------------------------------------- /php/licensekey.php: -------------------------------------------------------------------------------- 1 | make_license('GESTOBM30', 'Sandro Noel', 'sandro.noel@gesosoft.com'); 6 | 7 | // to verify a Key from cocoafob place it in here. 8 | 9 | 10 | echo "

Verify the cocoafob License
"; 11 | ?> 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | verify_license('GESTOBM30', 'Sandro Noel', 'sandro.noel@gesosoft.com',$key); 23 | } 24 | ?> 25 | 26 | private_key = file_get_contents('./dsa_priv.pem', FILE_USE_INCLUDE_PATH); 41 | //read the public Key from disk 42 | $this->public_key = file_get_contents('./dsa_pub.pem', FILE_USE_INCLUDE_PATH); 43 | }#-#constructor() 44 | 45 | #-############################################# 46 | # desc: Create a license 47 | public function make_license($product_code, $name, $email) 48 | { 49 | // Generae a sha1 digest with the passed parameters. 50 | $stringData = $product_code.",".$name.",".$email; 51 | echo "Data: ".$stringData."
"; 52 | $binary_signature =""; 53 | openssl_sign($stringData, $binary_signature, $this->private_key, OPENSSL_ALGO_SHA1); 54 | echo "Binary Sig: ".$binary_signature."
"; 55 | 56 | // base 32 encode the stuff 57 | $encoded = base32_encode($binary_signature); 58 | echo "Original Key: ". $encoded ."
"; 59 | echo "Key Length: ". strlen($encoded) ."
"; 60 | 61 | // replace O with 8 and I with 9 62 | $replacement = str_replace("O", "8", str_replace("I", "9", $encoded)); 63 | echo "Replaced: " .$replacement . "
"; 64 | 65 | //remove padding if any. 66 | $padding = trim(str_replace("=", "", $replacement)); 67 | echo "Stripped: " .$padding . "
"; 68 | 69 | 70 | $dashed = rtrim(chunk_split($padding, 5,"-")); 71 | $theKey = substr($dashed, 0 , strlen($dashed) -1); 72 | echo "Dashed: " .$theKey . "

"; 73 | 74 | 75 | 76 | echo "Verify the just created License
"; 77 | 78 | $this->verify_license($product_code, $name, $email, $theKey); 79 | 80 | return $theKey; 81 | } 82 | 83 | #-############################################# 84 | # desc: Verify License 85 | public function verify_license($product_code, $name, $email, $lic) 86 | { 87 | echo "Original: " .$lic . "
"; 88 | // replace O with 8 and I with 9 89 | $replacement = str_replace("8", "O", str_replace("9", "I", $lic)); 90 | echo "Replaced: " .$replacement . "
"; 91 | //remove Dashes. 92 | $undashed = trim(str_replace("-", "", $replacement)); 93 | echo "Undashed: " .$undashed . "
"; 94 | echo "Key Length: ". strlen($undashed) ."
"; 95 | // Pad the output length to a multiple of 8 with '=' characters 96 | $desiredLength = strlen($undashed); 97 | if($desiredLength % 8 != 0) { 98 | $desiredLength += (8 - ($desiredLength % 8)); 99 | $undashed = str_pad($undashed, $desiredLength, "="); 100 | } 101 | echo "padded: " .$undashed . "
"; 102 | // decode Key 103 | $decodedHash = base32_decode($undashed); 104 | echo "Binary Sig: ".$decodedHash. "
"; 105 | //digest the original Data 106 | $stringData = $product_code.",".$name.",".$email; 107 | $ok = openssl_verify($stringData, $decodedHash, $this->public_key, OPENSSL_ALGO_SHA1); 108 | if ($ok == 1) { 109 | echo "GOOD"; 110 | } elseif ($ok == 0) { 111 | echo "BAD"; 112 | } else { 113 | echo "ugly, error checking signature"; 114 | } 115 | } 116 | 117 | } # Class License 118 | ?> 119 | -------------------------------------------------------------------------------- /python/cocoafob.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from OpenSSL import crypto # requires at least version 0.15.2 (or the latest version from GitHub, as this one is not yet released as of 2015/11/11) 3 | 4 | # Creates a source string to generate the registration code with. 5 | # A source string the contains product code name and the user's registration name, 6 | # separated by a comma. 7 | def make_license_source(product_code, name): 8 | return (product_code + ',' + name).encode('utf8') 9 | 10 | # This method generates a registration code. It receives your private key, 11 | # a product code string and a registration name. 12 | def make_license(private_key_string, product_code, name): 13 | private_key = crypto.load_privatekey(crypto.FILETYPE_PEM, private_key_string) 14 | signature = crypto.sign(private_key, make_license_source(product_code, name), 'sha1') 15 | # Use sha1 instead of dss1 to avoid 'ValueError("No such digest method")' 16 | encoded_signature = base64.b32encode(signature).decode('utf8') 17 | # Replace 'O' with 8, 'I' with 9 18 | # See http://members.shaw.ca/akochoi-old/blog/2004/11-07/index.html 19 | encoded_signature = encoded_signature.replace('O', '8').replace('I', '9') 20 | # Remove equal signs 21 | encoded_signature = encoded_signature.replace('=', '') 22 | # Insert a dash every 5 characters 23 | encoded_signature = '-'.join([encoded_signature[i:i+5] for i in range(0, len(encoded_signature), 5)]) 24 | return encoded_signature 25 | 26 | def verify_license(public_key_string, encoded_signature, product_code, name): 27 | base32_signature = encoded_signature.replace('8', 'O').replace('9', 'I').replace('-', '') 28 | base32_signature += '=' * (8 - (len(base32_signature) % 8)) 29 | decoded_signature = base64.b32decode(base32_signature) 30 | public_key = crypto.load_publickey(crypto.FILETYPE_PEM, public_key_string) 31 | certificate = crypto.X509() 32 | certificate.set_pubkey(public_key) 33 | try: 34 | crypto.verify(certificate, decoded_signature, make_license_source(product_code, name), 'sha1') 35 | # Use sha1 instead of dss1 to avoid 'ValueError("No such digest method")' 36 | return True 37 | except: 38 | return False 39 | 40 | def main(): 41 | with open('../keys/privkey.pem') as keyfile: 42 | private_key_string = keyfile.read() 43 | with open('../keys/pubkey.pem') as keyfile: 44 | public_key_string = keyfile.read() 45 | 46 | # test generation 47 | license = make_license(private_key_string, 'product', 'user') 48 | print ('license is ' + license) 49 | 50 | # test verification 51 | assert(verify_license(public_key_string, license, 'product', 'user')) # This throws on an invalid license 52 | print ('verification successful') 53 | 54 | # test rejection of invalid signature 55 | assert(not verify_license(public_key_string, license, 'product', 'WRONGUSER')) 56 | print ('rejection successful') 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /python/license_server.py: -------------------------------------------------------------------------------- 1 | # Configurable mini license server with bare-bones logging. 2 | # See server.wsgi for deployment. 3 | # Usage: http://your-server/license/generate/?secret=donttellanybody&user=John%20Doe&email=john@doe.com 4 | 5 | from flask import Flask 6 | from flask import Response 7 | from flask import request 8 | import cocoafob 9 | import sqlite3 10 | 11 | app = Flask(__name__) 12 | app.enable_caching = True 13 | 14 | def generate_response(cursor, secret, user, email): 15 | if secret not in app.secrets: 16 | return Response(response='Error: Wrong secret.', status=503) 17 | 18 | if not user: 19 | return Response(response='Error: User is required.', status=400) 20 | 21 | if not email: 22 | return Response(response='Error: Email is required.', status=400) 23 | 24 | # Try to fetch an existing key from the DB. 25 | key = None 26 | if app.enable_caching: 27 | cursor.execute('SELECT response FROM requests WHERE secret = ? AND user = ? AND email = ? ORDER BY date ASC LIMIT 1', [secret, user, email]) 28 | key_row = cursor.fetchone() 29 | if key_row and len(key_row[0]) >= 60: # Crude check whether the response is a key and not an error message. 30 | key = key_row[0].encode('utf-8') 31 | 32 | if not key: 33 | # Need to read the private key and generate the key ourselves. 34 | with open(app.private_key_path) as keyfile: 35 | key = cocoafob.make_license(keyfile.read(), app.product, user) 36 | 37 | if key: 38 | return Response(response=key) 39 | else: 40 | return Response(response='Error: error generating the key.', status=500) 41 | 42 | def log_response(cursor, secret, user, email, response): 43 | cursor.execute('CREATE TABLE IF NOT EXISTS requests ' 44 | '(date DATETIME, secret TEXT, user TEXT, email TEXT, response TEXT)') 45 | cursor.execute('INSERT INTO requests VALUES (CURRENT_TIMESTAMP, ?, ?, ?, ?)', 46 | [secret, user, email, response.response[0]]) 47 | 48 | @app.route('/', methods=['GET']) 49 | def return_key(): 50 | secret = request.args.get('secret', '') 51 | user = request.args.get('user', '') 52 | email = request.args.get('email', '') 53 | db = sqlite3.connect(app.db_path) 54 | cursor = db.cursor() 55 | response = generate_response(cursor, secret, user, email) 56 | log_response(cursor, secret, user, email, response) 57 | db.commit() 58 | return response 59 | 60 | if __name__ == '__main__': 61 | # In production, use WSGI and configure these via server.wsgi! 62 | app.secrets = ['donttellanybody'] 63 | app.db_path = '../keys/requests.db' 64 | app.private_key_path = '../keys/privkey.pem' 65 | app.product = 'product' 66 | app.run(debug=True) 67 | -------------------------------------------------------------------------------- /python/server.wsgi: -------------------------------------------------------------------------------- 1 | # Sample server file for use with WSGI. 2 | # The corresponding Apache config would be (taken from http://flask.pocoo.org/docs/0.10/deploying/mod_wsgi/): 3 | # 4 | # WSGIDaemonProcess license_server user=timing group=timing threads=5 5 | # WSGIScriptAlias /license/generate /path/to/your/app/server.wsgi 6 | # 7 | # 8 | # WSGIProcessGroup license_server 9 | # WSGIApplicationGroup %{GLOBAL} 10 | # WSGIScriptReloading On 11 | # Require all granted 12 | # 13 | 14 | import sys 15 | sys.path.insert(0, '/path/to/your/app') 16 | 17 | from license_server import app as application 18 | 19 | application.secrets = ['some-random-string'] 20 | application.db_path = '/path/to/your/app/requests.db' 21 | application.private_key_path = '/path/to/your/app/privkey.pem' 22 | application.product = 'product' 23 | 24 | -------------------------------------------------------------------------------- /ruby/licensekey.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # Modify this file and put in your own license key generator 4 | # 5 | 6 | require "openssl" 7 | require "rubygems" 8 | require "base32" 9 | 10 | APPNAME = "the-archive" 11 | PRIVKEY = "path/to/privkey.pem" 12 | PUBKEY = "path/to/pubkey.pem" 13 | 14 | # Creates a source string to generate registration code. A source string 15 | # contains product code name and user's registration name. 16 | def make_license_source(product_code, name) 17 | product_code + "," + name 18 | end 19 | 20 | # This method is called by Potion Store to generate a registration code. It 21 | # receives a product code string, a registration name and quantity. I'm not 22 | # using quantity here, but you're free to do it. 23 | def make_license(product_code, name, copies) 24 | sign_dss1 = OpenSSL::Digest::SHA1.new 25 | priv = OpenSSL::PKey::DSA.new(File.read(PRIVKEY)) 26 | b32 = Base32.encode(priv.sign(sign_dss1, make_license_source(product_code, name))) 27 | # Replace Os with 8s and Is with 9s 28 | # See http://members.shaw.ca/akochoi-old/blog/2004/11-07/index.html 29 | b32.gsub!(/O/, '8') 30 | b32.gsub!(/I/, '9') 31 | # chop off trailing padding 32 | b32.delete("=").scan(/.{1,5}/).join("-") 33 | end 34 | 35 | def verify_license(product_code, name, copies, lic) 36 | verify_dss1 = OpenSSL::Digest::SHA1.new 37 | pub = OpenSSL::PKey::DSA.new(File.read(PUBKEY)) 38 | lic.delete!("-") 39 | lic.gsub!(/9/, 'I') 40 | lic.gsub!(/8/, 'O') 41 | # padded length has to divide by 8 42 | padded_length = lic.length%8 ? (lic.length/8 + 1)*8 : lic.length 43 | padded = lic + "=" * (padded_length-lic.length) 44 | pub.verify(verify_dss1, Base32.decode(padded), make_license_source(product_code, name)) 45 | end 46 | 47 | if ARGV.empty? 48 | # Simple command line test when called without any arguments 49 | require "test/unit" 50 | class TestPxLic < Test::Unit::TestCase 51 | def test_make_license 52 | 1000.times do 53 | lic = make_license(APPNAME, 'User Name', 10) 54 | puts lic 55 | assert verify_license(APPNAME, 'User Name', 10, lic), "Failed with #{lic}" 56 | end 57 | end 58 | end 59 | else 60 | # Generate license when called with 1+ argument (assuming it's a name) 61 | name = ARGV.pop 62 | lic = make_license(APPNAME, name, 1) 63 | puts name 64 | puts lic 65 | end 66 | -------------------------------------------------------------------------------- /swift4/CocoaFob.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /swift4/CocoaFob.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /swift4/CocoaFob.xcodeproj/xcshareddata/xcschemes/CocoaFob.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /swift4/CocoaFob/CFUtil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CFUtil.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 12/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func cfTry(_ err: CocoaFobError, cfBlock: (UnsafeMutablePointer?>) -> DarwinBoolean) throws { 12 | var cferr: Unmanaged? = nil 13 | if !cfBlock(&cferr).boolValue { 14 | if let cferr = cferr?.takeRetainedValue() { 15 | throw cferr 16 | } else { 17 | throw err 18 | } 19 | } 20 | } 21 | 22 | func cfTry(_ err: CocoaFobError, cfBlock: (UnsafeMutablePointer?>) -> T?) throws -> T { 23 | var cferr: Unmanaged? = nil 24 | if let result = cfBlock(&cferr) { 25 | return result 26 | } 27 | if let cferr = cferr?.takeRetainedValue() { 28 | throw cferr 29 | } else { 30 | throw err 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /swift4/CocoaFob/CocoaFob.h: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFob.h 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 05/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for CocoaFob. 12 | FOUNDATION_EXPORT double CocoaFobVersionNumber; 13 | 14 | //! Project version string for CocoaFob. 15 | FOUNDATION_EXPORT const unsigned char CocoaFobVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /swift4/CocoaFob/CocoaFobError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobError.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 05/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Custom error type: 13 | - Error: Unspecified error 14 | */ 15 | enum CocoaFobError: Error { 16 | case error 17 | } 18 | -------------------------------------------------------------------------------- /swift4/CocoaFob/CocoaFobLicGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobLicGenerator.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 05/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Generates CocoaFob registration keys 13 | */ 14 | public struct LicenseGenerator { 15 | 16 | var privKey: SecKey 17 | 18 | // MARK: - Initialization 19 | 20 | /** 21 | Initializes key generator with a private key in PEM format 22 | 23 | - parameter privateKeyPEM: String containing PEM representation of the private key 24 | */ 25 | public init?(privateKeyPEM: String) { 26 | let emptyString = "" as NSString 27 | let password = Unmanaged.passUnretained(emptyString as AnyObject) 28 | var params = SecItemImportExportKeyParameters( 29 | version: UInt32(bitPattern: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION), 30 | flags: SecKeyImportExportFlags.importOnlyOne, 31 | passphrase: password, 32 | alertTitle: Unmanaged.passUnretained(emptyString), 33 | alertPrompt: Unmanaged.passUnretained(emptyString), 34 | accessRef: nil, 35 | keyUsage: nil, 36 | keyAttributes: nil) 37 | var keyFormat = SecExternalFormat.formatPEMSequence 38 | var keyType = SecExternalItemType.itemTypePrivateKey 39 | guard let keyData = privateKeyPEM.data(using: String.Encoding.utf8) else { return nil } 40 | let keyBytes = [UInt8](keyData) 41 | let keyDataCF = CFDataCreate(nil, keyBytes, keyData.count)! 42 | var importArray: CFArray? = nil 43 | let osStatus = withUnsafeMutablePointer(to: &keyFormat, {pKeyFormat -> OSStatus in 44 | withUnsafeMutablePointer(to: &keyType, { pKeyType in 45 | SecItemImport(keyDataCF, nil, pKeyFormat, pKeyType, SecItemImportExportFlags(rawValue: 0), ¶ms, nil, &importArray) 46 | }) 47 | }) 48 | guard osStatus == errSecSuccess, importArray != nil else { return nil } 49 | let items = importArray! as NSArray 50 | guard items.count > 0 else { return nil } 51 | self.privKey = items[0] as! SecKey 52 | } 53 | 54 | // MARK: - Key generation 55 | 56 | /** 57 | Generates registration key for a user name 58 | 59 | - parameter userName: User name for which to generate a registration key 60 | - returns: Registration key 61 | */ 62 | public func generate(_ name: String) throws -> String { 63 | guard name != "" else { throw CocoaFobError.error } 64 | guard let nameData = getNameData(name) else { throw CocoaFobError.error } 65 | guard let signer = getSigner(nameData) else { throw CocoaFobError.error } 66 | guard let encoder = getEncoder() else { throw CocoaFobError.error } 67 | guard let group = connectTransforms(signer, encoder: encoder) else { throw CocoaFobError.error } 68 | let regDataCF = try cfTry(CocoaFobError.error) { return SecTransformExecute(group, $0) } 69 | guard let regData = regDataCF as? Data else { throw CocoaFobError.error } 70 | guard let reg = String(data: regData, encoding: .utf8) else { throw CocoaFobError.error } 71 | return reg.cocoaFobToReadableKey() 72 | } 73 | 74 | // MARK: - Utility functions 75 | 76 | fileprivate func connectTransforms(_ signer: SecTransform, encoder: SecTransform) -> SecGroupTransform? { 77 | guard let groupTransform = getGroupTransform() else { return nil } 78 | return SecTransformConnectTransforms(signer, kSecTransformOutputAttributeName, encoder, kSecTransformInputAttributeName, groupTransform, nil) 79 | } 80 | 81 | fileprivate func getGroupTransform() -> SecGroupTransform? { 82 | return SecTransformCreateGroupTransform() 83 | } 84 | 85 | func getNameData(_ name: String) -> Data? { 86 | return name.data(using: String.Encoding.utf8) 87 | } 88 | 89 | func getSigner(_ nameData: Data) -> SecTransform? { 90 | do { 91 | let signer = try cfTry(.error) { return SecSignTransformCreate(self.privKey, $0) } 92 | let _ = try cfTry(.error) { return SecTransformSetAttribute(signer, kSecTransformInputAttributeName, nameData as CFTypeRef, $0) } 93 | let _ = try cfTry(.error) { return SecTransformSetAttribute(signer, kSecDigestTypeAttribute, kSecDigestSHA1, $0) } 94 | return signer 95 | } catch { 96 | return nil 97 | } 98 | } 99 | 100 | fileprivate func getEncoder() -> SecTransform? { 101 | do { 102 | let encoder = try cfTry(.error) { return SecEncodeTransformCreate(kSecBase32Encoding, $0) } 103 | return encoder 104 | } catch { 105 | return nil 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /swift4/CocoaFob/CocoaFobLicVerifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobLicVerifier.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 12/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Verifies CocoaFob registration keys 13 | */ 14 | public struct LicenseVerifier { 15 | 16 | var pubKey: SecKey 17 | 18 | // MARK: - Initialization 19 | 20 | /** 21 | Initializes key verifier with a public key in PEM format 22 | 23 | - parameter publicKeyPEM: String containing PEM representation of the public key 24 | */ 25 | public init?(publicKeyPEM: String) { 26 | let emptyString = "" as NSString 27 | let password = Unmanaged.passUnretained(emptyString as AnyObject) 28 | var params = SecItemImportExportKeyParameters( 29 | version: UInt32(bitPattern: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION), 30 | flags: SecKeyImportExportFlags.importOnlyOne, 31 | passphrase: password, 32 | alertTitle: Unmanaged.passUnretained(emptyString), 33 | alertPrompt: Unmanaged.passUnretained(emptyString), 34 | accessRef: nil, 35 | keyUsage: nil, 36 | keyAttributes: nil) 37 | var keyFormat = SecExternalFormat.formatPEMSequence 38 | var keyType = SecExternalItemType.itemTypePublicKey 39 | guard let keyData = publicKeyPEM.data(using: String.Encoding.utf8) else { return nil } 40 | let keyBytes = [UInt8](keyData) 41 | let keyDataCF = CFDataCreate(nil, keyBytes, keyData.count)! 42 | var importArray: CFArray? = nil 43 | let osStatus = withUnsafeMutablePointer(to: &keyFormat) { pKeyFormat in 44 | withUnsafeMutablePointer(to: &keyType, {pKeyType in 45 | SecItemImport(keyDataCF, nil, pKeyFormat, pKeyType, SecItemImportExportFlags(rawValue: 0), ¶ms, nil, &importArray) 46 | }) 47 | } 48 | guard osStatus == errSecSuccess, importArray != nil else { return nil } 49 | let items = importArray! as NSArray 50 | guard items.count > 0 else { return nil } 51 | self.pubKey = items[0] as! SecKey 52 | } 53 | 54 | /** 55 | Verifies registration key against registered name. Doesn't throw since you are most likely not interested in the reason registration verification failed. 56 | 57 | - parameter regKey: Registration key string 58 | - parameter name: Registered name string 59 | - returns: `true` if the registration key is valid for the given name, `false` if not 60 | */ 61 | public func verify(_ regKey: String, forName name: String) -> Bool { 62 | do { 63 | guard let keyData = regKey.cocoaFobFromReadableKey().data(using: String.Encoding.utf8) else { return false } 64 | guard let nameData = name.data(using: String.Encoding.utf8) else { return false } 65 | let decoder = try getDecoder(keyData) 66 | let signature = try cfTry(.error) { SecTransformExecute(decoder, $0) } 67 | let verifier = try getVerifier(self.pubKey, signature: signature as! Data, nameData: nameData) 68 | let result = try cfTry(.error) { SecTransformExecute(verifier, $0) } 69 | let boolResult = result as! CFBoolean 70 | return Bool(truncating: boolResult) 71 | } catch { 72 | return false 73 | } 74 | } 75 | 76 | // MARK: - Helper functions 77 | 78 | fileprivate func getDecoder(_ keyData: Data) throws -> SecTransform { 79 | let decoder = try cfTry(.error) { return SecDecodeTransformCreate(kSecBase32Encoding, $0) } 80 | let _ = try cfTry(.error) { return SecTransformSetAttribute(decoder, kSecTransformInputAttributeName, keyData as CFTypeRef, $0) } 81 | return decoder 82 | } 83 | 84 | fileprivate func getVerifier(_ publicKey: SecKey, signature: Data, nameData: Data) throws -> SecTransform { 85 | let verifier = try cfTry(.error) { return SecVerifyTransformCreate(publicKey, signature as CFData?, $0) } 86 | let _ = try cfTry(.error) { return SecTransformSetAttribute(verifier, kSecTransformInputAttributeName, nameData as CFTypeRef, $0) } 87 | let _ = try cfTry(.error) { return SecTransformSetAttribute(verifier, kSecDigestTypeAttribute, kSecDigestSHA1, $0) } 88 | return verifier 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /swift4/CocoaFob/CocoaFobStringExt.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobStringExt.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 12/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | /** 14 | Converts generated CocoaFob key to its human-readable form: 15 | - replaces Os with 8s and Is with 9s 16 | - trims padding characters at the end 17 | - inserts dashes every 5 characters 18 | 19 | - returns: Human-readable registration key 20 | */ 21 | func cocoaFobToReadableKey() -> String { 22 | let replacedOwith8 = self.replacingOccurrences(of: "O", with: "8") 23 | let replacedIwith9 = replacedOwith8.replacingOccurrences(of: "I", with: "9") 24 | var key = replacedIwith9.replacingOccurrences(of: "=", with: "") 25 | 26 | var index = 5 27 | while index < key.utf8.count { 28 | key.insert(contentsOf: ["-"], at: key.index(key.startIndex, offsetBy: index)) 29 | index += 6 30 | } 31 | 32 | return key 33 | } 34 | 35 | /** 36 | Reverses readability changes to a supplied key: 37 | - removes dashes 38 | - replaces 9s with Is and 8s with Os 39 | 40 | - returns: Compacted key ready for verification 41 | */ 42 | func cocoaFobFromReadableKey() -> String { 43 | let replaced9withI = self.replacingOccurrences(of: "9", with: "I") 44 | let replaced8withO = replaced9withI.replacingOccurrences(of: "8", with: "O") 45 | let key = replaced8withO.replacingOccurrences(of: "-", with: "") 46 | return key 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /swift4/CocoaFob/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 PixelEspresso. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /swift4/CocoaFobTests/CocoaFobTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobTests.swift 3 | // CocoaFobTests 4 | // 5 | // Created by Gleb Dolgich on 05/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import CocoaFob 11 | 12 | class CocoaFobTests: XCTestCase { 13 | 14 | let privateKeyPEM = "-----BEGIN DSA PRIVATE KEY-----\n" 15 | + "MIH5AgEAAkEA8wm04e0QcQRoAVJWWnUw/4rQEKbLKjujJu6oyEv7Y2oT3itY5pbO\n" 16 | + "bgYCHEu9FBizqq7apsWYSF3YXiRjKlg10wIVALfs9eVL10PhoV6zczFpi3C7FzWN\n" 17 | + "AkBaPhALEKlgIltHsumHdTSBqaVoR1/bmlgw/BCC13IAsW40nkFNsK1OVwjo2ocn\n" 18 | + "3MwW4Rdq6uLm3DlENRZ5bYrTAkEA4reDYZKAl1vx+8EIMP/+2Z7ekydHfX0sTMDg\n" 19 | + "kxhtRm6qtcywg01X847Y9ySgNepqleD+Ka2Wbucj1pOry8MoDQIVAIXgAB9GBLh4\n" 20 | + "keUwLHBtpClnD5E8\n" 21 | + "-----END DSA PRIVATE KEY-----\n" 22 | 23 | let publicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" 24 | + "MIHxMIGoBgcqhkjOOAQBMIGcAkEA8wm04e0QcQRoAVJWWnUw/4rQEKbLKjujJu6o\n" 25 | + "yEv7Y2oT3itY5pbObgYCHEu9FBizqq7apsWYSF3YXiRjKlg10wIVALfs9eVL10Ph\n" 26 | + "oV6zczFpi3C7FzWNAkBaPhALEKlgIltHsumHdTSBqaVoR1/bmlgw/BCC13IAsW40\n" 27 | + "nkFNsK1OVwjo2ocn3MwW4Rdq6uLm3DlENRZ5bYrTA0QAAkEA4reDYZKAl1vx+8EI\n" 28 | + "MP/+2Z7ekydHfX0sTMDgkxhtRm6qtcywg01X847Y9ySgNepqleD+Ka2Wbucj1pOr\n" 29 | + "y8MoDQ==\n" 30 | + "-----END PUBLIC KEY-----\n" 31 | 32 | func testInitGeneratorPass() { 33 | let keygen = LicenseGenerator(privateKeyPEM: privateKeyPEM) 34 | XCTAssertNotNil(keygen?.privKey) 35 | } 36 | 37 | func testInitGeneratorFail() { 38 | let privateKeyPEM = "-----BEGIN DSA PRIVATE KEY-----\n" 39 | let keygen = LicenseGenerator(privateKeyPEM: privateKeyPEM) 40 | XCTAssert(keygen == nil) 41 | } 42 | 43 | func testGetNameData() { 44 | let keygen = LicenseGenerator(privateKeyPEM: privateKeyPEM) 45 | XCTAssertNotNil(keygen?.privKey) 46 | let name = "Joe Bloggs" 47 | let nameData = keygen?.getNameData(name) 48 | XCTAssertNotNil(nameData) 49 | if let nameData_ = nameData { 50 | let nameFromDataAsNSString = NSString(data: nameData_, encoding: String.Encoding.utf8.rawValue) 51 | XCTAssertNotNil(nameFromDataAsNSString) 52 | let nameFromData = String(nameFromDataAsNSString!) 53 | XCTAssertEqual(nameFromData, name) 54 | } 55 | } 56 | 57 | func testGetSignerPass() { 58 | let keygen = LicenseGenerator(privateKeyPEM: privateKeyPEM) 59 | XCTAssertNotNil(keygen?.privKey) 60 | let name = "Joe Bloggs" 61 | let nameData = keygen?.getNameData(name) 62 | XCTAssertNotNil(nameData) 63 | let signer = keygen?.getSigner(nameData!) 64 | XCTAssertNotNil(signer) 65 | 66 | // check name attribute 67 | if let signer_ = signer { 68 | let nameDataAttr = SecTransformGetAttribute(signer_, kSecTransformInputAttributeName) 69 | XCTAssertNotNil(nameDataAttr) 70 | let nameDataFromAttr = nameDataAttr! as! Data 71 | XCTAssertNotNil(nameDataFromAttr, "Expected to get name data back") 72 | let nameFromAttrAsNSString = NSString(data: nameDataFromAttr, encoding: String.Encoding.utf8.rawValue) 73 | XCTAssertNotNil(nameFromAttrAsNSString) 74 | let nameFromAttr = String(nameFromAttrAsNSString!) 75 | XCTAssertEqual(nameFromAttr, name) 76 | 77 | // check digest type attribute 78 | let digestTypeAttr = SecTransformGetAttribute(signer!, kSecDigestTypeAttribute) 79 | XCTAssertNotNil(digestTypeAttr) 80 | let digestTypeFromAttr = digestTypeAttr! as! NSString 81 | XCTAssertNotNil(digestTypeFromAttr, "Expected to get SHA1 transform type back") 82 | XCTAssertEqual(digestTypeFromAttr, kSecDigestSHA1 as NSString) 83 | } 84 | } 85 | 86 | func testToReadableKeyPass() { 87 | let unreadable = "GAWAEFCDW3KH4IP5E2DHKUHPQPN5P52V43SVGDYCCRS64XXNRYBBCT44EOGM3SKYV4272LQ6LQ======" 88 | let expected = "GAWAE-FCDW3-KH49P-5E2DH-KUHPQ-PN5P5-2V43S-VGDYC-CRS64-XXNRY-BBCT4-4E8GM-3SKYV-4272L-Q6LQ" 89 | let actual = unreadable.cocoaFobToReadableKey() 90 | XCTAssertEqual(actual, expected) 91 | } 92 | 93 | func testFromReadableKeyPass() { 94 | let readable = "GAWAE-FCDW3-KH49P-5E2DH-KUHPQ-PN5P5-2V43S-VGDYC-CRS64-XXNRY-BBCT4-4E8GM-3SKYV-4272L-Q6LQ" 95 | let expected = "GAWAEFCDW3KH4IP5E2DHKUHPQPN5P52V43SVGDYCCRS64XXNRYBBCT44EOGM3SKYV4272LQ6LQ" 96 | let actual = readable.cocoaFobFromReadableKey() 97 | XCTAssertEqual(actual, expected) 98 | } 99 | 100 | func testGeneratePass() { 101 | let keygen = LicenseGenerator(privateKeyPEM: privateKeyPEM) 102 | XCTAssertNotNil(keygen?.privKey) 103 | do { 104 | if let actual = try keygen?.generate("Joe Bloggs") { 105 | print(actual) 106 | XCTAssert(actual != "") 107 | } 108 | } catch { 109 | XCTAssert(false, "\(error)") 110 | } 111 | } 112 | 113 | func testInitVerifierPass() { 114 | let verifier = LicenseVerifier(publicKeyPEM: publicKeyPEM) 115 | XCTAssertNotNil(verifier?.pubKey) 116 | } 117 | 118 | func testInitVerifierFail() { 119 | let publicKeyPEM = "-----BEGIN PUBLIC KEY-----\n" 120 | let keychecker = LicenseVerifier(publicKeyPEM: publicKeyPEM) 121 | XCTAssertNil(keychecker?.pubKey) 122 | } 123 | 124 | func testVerifyPass() { 125 | let verifier = LicenseVerifier(publicKeyPEM: publicKeyPEM) 126 | XCTAssertNotNil(verifier?.pubKey) 127 | let name = "Joe Bloggs" 128 | let regKey = "GAWQE-F9AQP-XJCCL-PAFAX-NU5XX-EUG6W-KLT3H-VTEB9-A9KHJ-8DZ5R-DL74G-TU4BN-7ATPY-3N4XB-V4V27-Q" 129 | let result = verifier?.verify(regKey, forName: name) ?? false 130 | XCTAssertTrue(result) 131 | } 132 | 133 | func testVerifyBadNameFail() { 134 | let verifier = LicenseVerifier(publicKeyPEM: publicKeyPEM) 135 | XCTAssertNotNil(verifier?.pubKey) 136 | let name = "Joe Bloggs II" 137 | let regKey = "GAWQE-F9AQP-XJCCL-PAFAX-NU5XX-EUG6W-KLT3H-VTEB9-A9KHJ-8DZ5R-DL74G-TU4BN-7ATPY-3N4XB-V4V27-Q" 138 | let result = verifier?.verify(regKey, forName: name) ?? false 139 | XCTAssertFalse(result) 140 | } 141 | 142 | func testVerifyBadKeyFail() { 143 | let verifier = LicenseVerifier(publicKeyPEM: publicKeyPEM) 144 | XCTAssertNotNil(verifier?.pubKey) 145 | let name = "Joe Bloggs" 146 | let regKey = "foo bar" 147 | let result = verifier?.verify(regKey, forName: name) ?? false 148 | XCTAssertFalse(result) 149 | } 150 | 151 | func testVerifyEmptyKeyFail() { 152 | let verifier = LicenseVerifier(publicKeyPEM: publicKeyPEM) 153 | XCTAssertNotNil(verifier?.pubKey) 154 | let name = "Joe Bloggs" 155 | let regKey = "" 156 | let result = verifier?.verify(regKey, forName: name) ?? false 157 | XCTAssertFalse(result) 158 | } 159 | 160 | func testVerifyEmptyNameAndKeyFail() { 161 | let verifier = LicenseVerifier(publicKeyPEM: publicKeyPEM) 162 | XCTAssertNotNil(verifier?.pubKey) 163 | let name = "" 164 | let regKey = "" 165 | let result = verifier?.verify(regKey, forName: name) ?? false 166 | XCTAssertFalse(result) 167 | } 168 | 169 | func testVerifyEmptyNameFail() { 170 | let verifier = LicenseVerifier(publicKeyPEM: publicKeyPEM) 171 | XCTAssertNotNil(verifier?.pubKey) 172 | let name = "" 173 | let regKey = "GAWQE-F9AQP-XJCCL-PAFAX-NU5XX-EUG6W-KLT3H-VTEB9-A9KHJ-8DZ5R-DL74G-TU4BN-7ATPY-3N4XB-V4V27-Q" 174 | let result = verifier?.verify(regKey, forName: name) ?? false 175 | XCTAssertFalse(result) 176 | } 177 | 178 | func testGenerateAndVerifyPass() { 179 | let keygen = LicenseGenerator(privateKeyPEM: privateKeyPEM) 180 | XCTAssertTrue(keygen != nil) 181 | XCTAssertNotNil(keygen?.privKey) 182 | let name = "Joe Bloggs" 183 | do { 184 | if let regKey = try keygen?.generate(name) { 185 | let verifier = LicenseVerifier(publicKeyPEM: publicKeyPEM) 186 | XCTAssertNotNil(verifier?.pubKey) 187 | let result = verifier?.verify(regKey, forName: name) ?? false 188 | XCTAssertTrue(result) 189 | } 190 | } catch { 191 | XCTAssert(false, "\(error)") 192 | } 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /swift4/CocoaFobTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /swift4/README.md: -------------------------------------------------------------------------------- 1 | # CocoaFob Swift 4.0 Port 2 | 3 | For a version targeting Swift 5, see https://github.com/glebd/cocoafob/tree/master/swift5 4 | 5 | ## Instructions 6 | 7 | Add the necessary files directly to your project. For your app they are: 8 | 9 | * `CocoaFob/CFUtil.swift` 10 | * `CocoaFob/CocoaFobError.swift` 11 | * `CocoaFob/CocoaFobLicVerifier.swift` 12 | * `CocoaFob/CocoaFobStringExt.swift` 13 | 14 | Generate your DSA key as described in the main README and add the public key as a resource to your app. 15 | 16 | Look at the tests in `CocoaFobTests/CocoaFobTests.swift` to see how to verify a registration key. 17 | 18 | ## Build 19 | 20 | To build and install the `cocoafob-keygen` command-line utility for generating and verifying registration keys, execute the following command in the `swift` subdirectory of the CocoaFob project: 21 | 22 | ```bash 23 | xcodebuild -target cocoafob-keygen install 24 | ``` 25 | 26 | The utility will be installed in `/usr/local/bin`. 27 | 28 | ## Dependencies 29 | 30 | * CommandLine by Ben Gollmer, GitHub -- https://github.com/jatoben/CommandLine 31 | 32 | ```bash 33 | git subtree {add|pull} --squash --prefix swift/vendor/CommandLine git://github.com/jatoben/CommandLine.git master 34 | ``` 35 | -------------------------------------------------------------------------------- /swift4/cocoafob-keygen/Functions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Functions.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 16/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func verifyRegKey(pubKeyPath: String, userName: String, regKey: String) throws -> Bool { 12 | let pubKeyPEM = try NSString(contentsOfFile: pubKeyPath, encoding: NSUTF8StringEncoding) as String 13 | if let verifier = CocoaFobLicVerifier(publicKeyPEM: pubKeyPEM) { 14 | return verifier.verify(regKey, forName: userName) 15 | } 16 | return false 17 | } 18 | 19 | func generateRegKey(pvtKeyPath: String, userName: String) throws -> String { 20 | let pvtKeyPEM = try NSString(contentsOfFile: pvtKeyPath, encoding: NSUTF8StringEncoding) as String 21 | if let generator = CocoaFobLicGenerator(privateKeyPEM: pvtKeyPEM) { 22 | return try generator.generate(userName) 23 | } 24 | throw CocoaFobError.Error 25 | } 26 | 27 | func generateRegURL(pvtKeyPath: String, userName: String, schema: String) throws -> String? { 28 | let regKey = try generateRegKey(pvtKeyPath, userName: userName) 29 | if let userNameData = userName.dataUsingEncoding(NSUTF8StringEncoding) { 30 | let userNameBase64 = userNameData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) 31 | return "\(schema)://\(userNameBase64)/\(regKey)" 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /swift4/cocoafob-keygen/Stderr.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StderrOutputStream.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 16/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // From http://ericasadun.com/2015/06/09/swift-2-0-how-to-print/ 12 | public struct Stderr: OutputStreamType { 13 | public mutating func write(string: String) { 14 | fputs(string, stderr) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /swift4/cocoafob-keygen/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // cocoafob-keygen 4 | // 5 | // Created by Gleb Dolgich on 14/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let pubKey = StringOption(longFlag: "pubkey", helpMessage: "Public DSA key file path to verify registration") 12 | let pvtKey = StringOption(longFlag: "pvtkey", helpMessage: "Private DSA key file path to generate registration") 13 | let userName = StringOption(shortFlag: "u", longFlag: "username", helpMessage: "User name") 14 | let regKey = StringOption(shortFlag: "r", longFlag: "regkey", helpMessage: "Registration key to verify") 15 | let urlSchema = StringOption(longFlag: "schema", helpMessage: "URL schema for registration links (if given a URL will be generated)") 16 | let help = BoolOption(shortFlag: "h", longFlag: "help", helpMessage: "Print help message") 17 | 18 | print("CocoaFob Key Generator, Version 1.0 -- Copyright (C) 2015 PixelEspresso") 19 | 20 | let cli = CommandLine() 21 | cli.addOptions(pubKey, pvtKey, userName, regKey, urlSchema, help) 22 | 23 | var errStream = Stderr() 24 | 25 | do { 26 | try cli.parse() 27 | } catch { 28 | cli.printUsage(error) 29 | exit(EX_USAGE) 30 | } 31 | 32 | if let pub = pubKey.value { 33 | 34 | if let user = userName.value, let reg = regKey.value { 35 | do { 36 | print("Name: \(user)\n Key: \(reg)") 37 | if try verifyRegKey(pub, userName: user, regKey: reg) { 38 | print("Registration is VALID") 39 | } else { 40 | print("Registration is INVALID") 41 | exit(1) 42 | } 43 | } catch { 44 | print("ERROR: Unable to verify registration key -- \(error)", &errStream) 45 | exit(EX_DATAERR) 46 | } 47 | } else { 48 | print("ERROR: Specifying a public key means 'verify' and requires both user name and registration key", &errStream) 49 | cli.printUsage() 50 | exit(EX_USAGE) 51 | } 52 | 53 | } else if let pvt = pvtKey.value { 54 | 55 | if let user = userName.value { 56 | if regKey.value != nil { 57 | print("WARNING: Specifying a private key means 'generate' and doesn't need a registration key", &errStream) 58 | } 59 | do { 60 | if let schema = urlSchema.value { 61 | if let url = try generateRegURL(pvt, userName: user, schema: schema) { 62 | print(url) 63 | } else { 64 | print("ERROR: Unable to generate registration URL") 65 | } 66 | } else { 67 | let reg = try generateRegKey(pvt, userName: user) 68 | print("Name: \(user)\n Key: \(reg)") 69 | } 70 | } catch { 71 | print("ERROR: Unable to generate registration key -- \(error)", &errStream) 72 | exit(EX_DATAERR) 73 | } 74 | } else { 75 | print("ERROR: Specifying a private key means 'verify' and requires a user name", &errStream) 76 | cli.printUsage() 77 | exit(EX_USAGE) 78 | } 79 | 80 | } else { 81 | 82 | if help.value { 83 | cli.printUsage() 84 | } else { 85 | print("ERROR: Either private or public key must be provided", &errStream) 86 | cli.printUsage() 87 | exit(EX_USAGE) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /swift4/vendor/CommandLine/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | -------------------------------------------------------------------------------- /swift4/vendor/CommandLine/.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | xcode_project: CommandLine.xcodeproj 3 | xcode_scheme: CommandLine 4 | script: xcodebuild -scheme CommandLine test 5 | osx_image: beta-xcode6.3 6 | 7 | # Disable all builds until Travis supports Swift 2.0 8 | branches: 9 | only: [] 10 | -------------------------------------------------------------------------------- /swift4/vendor/CommandLine/CommandLine.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /swift4/vendor/CommandLine/CommandLine/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSHumanReadableCopyright 24 | Copyright © 2014 Ben Gollmer. Licensed under the Apache License, Version 2.0. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /swift4/vendor/CommandLine/CommandLine/Option.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Option.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /** 19 | * The base class for a command-line option. 20 | */ 21 | public class Option { 22 | public let shortFlag: String? 23 | public let longFlag: String? 24 | public let required: Bool 25 | public let helpMessage: String 26 | 27 | /** True if the option was set when parsing command-line arguments */ 28 | public var wasSet: Bool { 29 | return false 30 | } 31 | 32 | public var flagDescription: String { 33 | switch (shortFlag, longFlag) { 34 | case (let sf, let lf) where sf != nil && lf != nil: 35 | return "\(ShortOptionPrefix)\(sf!), \(LongOptionPrefix)\(lf!)" 36 | case (_, let lf) where lf != nil: 37 | return "\(LongOptionPrefix)\(lf!)" 38 | default: 39 | return "\(ShortOptionPrefix)\(shortFlag!)" 40 | } 41 | } 42 | 43 | private init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) { 44 | if let sf = shortFlag { 45 | assert(sf.characters.count == 1, "Short flag must be a single character") 46 | assert(Int(sf) == nil && sf.toDouble() == nil, "Short flag cannot be a numeric value") 47 | } 48 | 49 | if let lf = longFlag { 50 | assert(Int(lf) == nil && lf.toDouble() == nil, "Long flag cannot be a numeric value") 51 | } 52 | 53 | self.shortFlag = shortFlag 54 | self.longFlag = longFlag 55 | self.helpMessage = helpMessage 56 | self.required = required 57 | } 58 | 59 | /* The optional casts in these initalizers force them to call the private initializer. Without 60 | * the casts, they recursively call themselves. 61 | */ 62 | 63 | /** Initializes a new Option that has both long and short flags. */ 64 | public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) { 65 | self.init(shortFlag as String?, longFlag, required, helpMessage) 66 | } 67 | 68 | /** Initializes a new Option that has only a short flag. */ 69 | public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) { 70 | self.init(shortFlag as String?, nil, required, helpMessage) 71 | } 72 | 73 | /** Initializes a new Option that has only a long flag. */ 74 | public convenience init(longFlag: String, required: Bool = false, helpMessage: String) { 75 | self.init(nil, longFlag as String?, required, helpMessage) 76 | } 77 | 78 | func flagMatch(flag: String) -> Bool { 79 | return flag == shortFlag || flag == longFlag 80 | } 81 | 82 | func setValue(values: [String]) -> Bool { 83 | return false 84 | } 85 | } 86 | 87 | /** 88 | * A boolean option. The presence of either the short or long flag will set the value to true; 89 | * absence of the flag(s) is equivalent to false. 90 | */ 91 | public class BoolOption: Option { 92 | private var _value: Bool = false 93 | 94 | public var value: Bool { 95 | return _value 96 | } 97 | 98 | override public var wasSet: Bool { 99 | return _value 100 | } 101 | 102 | override func setValue(values: [String]) -> Bool { 103 | _value = true 104 | return true 105 | } 106 | } 107 | 108 | /** An option that accepts a positive or negative integer value. */ 109 | public class IntOption: Option { 110 | private var _value: Int? 111 | 112 | public var value: Int? { 113 | return _value 114 | } 115 | 116 | override public var wasSet: Bool { 117 | return _value != nil 118 | } 119 | 120 | override func setValue(values: [String]) -> Bool { 121 | if values.count == 0 { 122 | return false 123 | } 124 | 125 | if let val = Int(values[0]) { 126 | _value = val 127 | return true 128 | } 129 | 130 | return false 131 | } 132 | } 133 | 134 | /** 135 | * An option that represents an integer counter. Each time the short or long flag is found 136 | * on the command-line, the counter will be incremented. 137 | */ 138 | public class CounterOption: Option { 139 | private var _value: Int = 0 140 | 141 | public var value: Int { 142 | return _value 143 | } 144 | 145 | override public var wasSet: Bool { 146 | return _value > 0 147 | } 148 | 149 | override func setValue(values: [String]) -> Bool { 150 | _value += 1 151 | return true 152 | } 153 | } 154 | 155 | /** An option that accepts a positive or negative floating-point value. */ 156 | public class DoubleOption: Option { 157 | private var _value: Double? 158 | 159 | public var value: Double? { 160 | return _value 161 | } 162 | 163 | override public var wasSet: Bool { 164 | return _value != nil 165 | } 166 | 167 | override func setValue(values: [String]) -> Bool { 168 | if values.count == 0 { 169 | return false 170 | } 171 | 172 | if let val = values[0].toDouble() { 173 | _value = val 174 | return true 175 | } 176 | 177 | return false 178 | } 179 | } 180 | 181 | /** An option that accepts a string value. */ 182 | public class StringOption: Option { 183 | private var _value: String? = nil 184 | 185 | public var value: String? { 186 | return _value 187 | } 188 | 189 | override public var wasSet: Bool { 190 | return _value != nil 191 | } 192 | 193 | override func setValue(values: [String]) -> Bool { 194 | if values.count == 0 { 195 | return false 196 | } 197 | 198 | _value = values[0] 199 | return true 200 | } 201 | } 202 | 203 | /** An option that accepts one or more string values. */ 204 | public class MultiStringOption: Option { 205 | private var _value: [String]? 206 | 207 | public var value: [String]? { 208 | return _value 209 | } 210 | 211 | override public var wasSet: Bool { 212 | return _value != nil 213 | } 214 | 215 | override func setValue(values: [String]) -> Bool { 216 | if values.count == 0 { 217 | return false 218 | } 219 | 220 | _value = values 221 | return true 222 | } 223 | } 224 | 225 | /** An option that represents an enum value. */ 226 | public class EnumOption: Option { 227 | private var _value: T? 228 | public var value: T? { 229 | return _value 230 | } 231 | 232 | override public var wasSet: Bool { 233 | return _value != nil 234 | } 235 | 236 | /* Re-defining the intializers is necessary to make the Swift 2 compiler happy, as 237 | * of Xcode 7 beta 2. 238 | */ 239 | 240 | private override init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) { 241 | super.init(shortFlag, longFlag, required, helpMessage) 242 | } 243 | 244 | /** Initializes a new Option that has both long and short flags. */ 245 | public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) { 246 | self.init(shortFlag as String?, longFlag, required, helpMessage) 247 | } 248 | 249 | /** Initializes a new Option that has only a short flag. */ 250 | public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) { 251 | self.init(shortFlag as String?, nil, required, helpMessage) 252 | } 253 | 254 | /** Initializes a new Option that has only a long flag. */ 255 | public convenience init(longFlag: String, required: Bool = false, helpMessage: String) { 256 | self.init(nil, longFlag as String?, required, helpMessage) 257 | } 258 | 259 | override func setValue(values: [String]) -> Bool { 260 | if values.count == 0 { 261 | return false 262 | } 263 | 264 | if let v = T(rawValue: values[0]) { 265 | _value = v 266 | return true 267 | } 268 | 269 | return false 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /swift4/vendor/CommandLine/CommandLine/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * StringExtensions.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* Required for localeconv(3) */ 19 | import Darwin 20 | 21 | internal extension String { 22 | /* Retrieves locale-specified decimal separator from the environment 23 | * using localeconv(3). 24 | */ 25 | private func _localDecimalPoint() -> Character { 26 | let locale = localeconv() 27 | if locale != nil { 28 | let decimalPoint = locale.memory.decimal_point 29 | if decimalPoint != nil { 30 | return Character(UnicodeScalar(UInt32(decimalPoint.memory))) 31 | } 32 | } 33 | 34 | return "." 35 | } 36 | 37 | /** 38 | * Attempts to parse the string value into a Double. 39 | * 40 | * - returns: A Double if the string can be parsed, nil otherwise. 41 | */ 42 | func toDouble() -> Double? { 43 | var characteristic: String = "0" 44 | var mantissa: String = "0" 45 | var inMantissa: Bool = false 46 | var isNegative: Bool = false 47 | let decimalPoint = self._localDecimalPoint() 48 | 49 | for (i, c) in self.characters.enumerate() { 50 | if i == 0 && c == "-" { 51 | isNegative = true 52 | continue 53 | } 54 | 55 | if c == decimalPoint { 56 | inMantissa = true 57 | continue 58 | } 59 | 60 | if Int(String(c)) != nil { 61 | if !inMantissa { 62 | characteristic.append(c) 63 | } else { 64 | mantissa.append(c) 65 | } 66 | } else { 67 | /* Non-numeric character found, bail */ 68 | return nil 69 | } 70 | } 71 | 72 | return (Double(Int(characteristic)!) + 73 | Double(Int(mantissa)!) / pow(Double(10), Double(mantissa.characters.count - 1))) * 74 | (isNegative ? -1 : 1) 75 | } 76 | 77 | /** 78 | * Splits a string into an array of string components. 79 | * 80 | * - parameter splitBy: The character to split on. 81 | * - parameter maxSplit: The maximum number of splits to perform. If 0, all possible splits are made. 82 | * 83 | * - returns: An array of string components. 84 | */ 85 | func splitByCharacter(splitBy: Character, maxSplits: Int = 0) -> [String] { 86 | var s = [String]() 87 | var numSplits = 0 88 | 89 | var curIdx = self.startIndex 90 | for(var i = self.startIndex; i != self.endIndex; i = i.successor()) { 91 | let c = self[i] 92 | if c == splitBy && (maxSplits == 0 || numSplits < maxSplits) { 93 | s.append(self[Range(start: curIdx, end: i)]) 94 | curIdx = i.successor() 95 | numSplits++ 96 | } 97 | } 98 | 99 | if curIdx != self.endIndex { 100 | s.append(self[Range(start: curIdx, end: self.endIndex)]) 101 | } 102 | 103 | return s 104 | } 105 | 106 | /** 107 | * Pads a string to the specified width. 108 | * 109 | * - parameter width: The width to pad the string to. 110 | * - parameter padBy: The character to use for padding. 111 | * 112 | * - returns: A new string, padded to the given width. 113 | */ 114 | func paddedToWidth(width: Int, padBy: Character = " ") -> String { 115 | var s = self 116 | var currentLength = self.characters.count 117 | 118 | while currentLength++ < width { 119 | s.append(padBy) 120 | } 121 | 122 | return s 123 | } 124 | 125 | /** 126 | * Wraps a string to the specified width. 127 | * 128 | * This just does simple greedy word-packing, it doesn't go full Knuth-Plass. 129 | * If a single word is longer than the line width, it will be placed (unsplit) 130 | * on a line by itself. 131 | * 132 | * - parameter width: The maximum length of a line. 133 | * - parameter wrapBy: The line break character to use. 134 | * - parameter splitBy: The character to use when splitting the string into words. 135 | * 136 | * - returns: A new string, wrapped at the given width. 137 | */ 138 | func wrappedAtWidth(width: Int, wrapBy: Character = "\n", splitBy: Character = " ") -> String { 139 | var s = "" 140 | var currentLineWidth = 0 141 | 142 | for word in self.splitByCharacter(splitBy) { 143 | let wordLength = word.characters.count 144 | 145 | if currentLineWidth + wordLength + 1 > width { 146 | /* Word length is greater than line length, can't wrap */ 147 | if wordLength >= width { 148 | s += word 149 | } 150 | 151 | s.append(wrapBy) 152 | currentLineWidth = 0 153 | } 154 | 155 | currentLineWidth += wordLength + 1 156 | s += word 157 | s.append(splitBy) 158 | } 159 | 160 | return s 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /swift4/vendor/CommandLine/CommandLineTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2014 Ben Gollmer. Licensed under the Apache License, Version 2.0. 25 | 26 | 27 | -------------------------------------------------------------------------------- /swift4/vendor/CommandLine/CommandLineTests/StringExtensionTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * StringExtensionTests.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import XCTest 19 | import CommandLine 20 | 21 | class StringExtensionTests: XCTestCase { 22 | 23 | func testToDouble() { 24 | /* Regular ol' double */ 25 | let a = "3.14159".toDouble() 26 | XCTAssertEqual(a!, 3.14159, "Failed to parse pi as double") 27 | 28 | let b = "-98.23".toDouble() 29 | XCTAssertEqual(b!, -98.23, "Failed to parse negative double") 30 | 31 | /* Ints should be parsable as doubles */ 32 | let c = "5".toDouble() 33 | XCTAssertEqual(c!, 5, "Failed to parse int as double") 34 | 35 | let d = "-2099".toDouble() 36 | XCTAssertEqual(d!, -2099, "Failed to parse negative int as double") 37 | 38 | 39 | /* Zero handling */ 40 | let e = "0.0".toDouble() 41 | XCTAssertEqual(e!, 0, "Failed to parse zero double") 42 | 43 | let f = "0".toDouble() 44 | XCTAssertEqual(f!, 0, "Failed to parse zero int as double") 45 | 46 | let g = "0.0000000000000000".toDouble() 47 | XCTAssertEqual(g!, 0, "Failed to parse very long zero double") 48 | 49 | let h = "-0.0".toDouble() 50 | XCTAssertEqual(h!, 0, "Failed to parse negative zero double") 51 | 52 | let i = "-0".toDouble() 53 | XCTAssertEqual(i!, 0, "Failed to parse negative zero int as double") 54 | 55 | let j = "-0.000000000000000".toDouble() 56 | XCTAssertEqual(j!, 0, "Failed to parse very long negative zero double") 57 | 58 | 59 | /* Various extraneous chars */ 60 | let k = "+42.3".toDouble() 61 | XCTAssertNil(k, "Parsed double with extraneous +") 62 | 63 | let l = " 827.2".toDouble() 64 | XCTAssertNil(l, "Parsed double with extraneous space") 65 | 66 | let m = "283_3".toDouble() 67 | XCTAssertNil(m, "Parsed double with extraneous _") 68 | 69 | let n = "💩".toDouble() 70 | XCTAssertNil(n, "Parsed poo") 71 | 72 | /* Locale handling */ 73 | setlocale(LC_NUMERIC, "sv_SE.UTF-8") 74 | 75 | let o = "888,8".toDouble() 76 | XCTAssertEqual(o!, 888.8, "Failed to parse double in alternate locale") 77 | 78 | let p = "888.8".toDouble() 79 | XCTAssertNil(p, "Parsed double in alternate locale with wrong decimal point") 80 | 81 | /* Set locale back so as not to perturb any other tests */ 82 | setlocale(LC_NUMERIC, "") 83 | } 84 | 85 | func testSplitByCharacter() { 86 | let a = "1,2,3".splitByCharacter(",") 87 | XCTAssertEqual(a.count, 3, "Failed to split into correct number of components") 88 | 89 | let b = "123".splitByCharacter(",") 90 | XCTAssertEqual(b.count, 1, "Failed to split when separator not found") 91 | 92 | let c = "".splitByCharacter(",") 93 | XCTAssertEqual(c.count, 0, "Splitting empty string should return empty array") 94 | 95 | let e = "a-b-c-d".splitByCharacter("-", maxSplits: 2) 96 | XCTAssertEqual(e.count, 3, "Failed to limit splits") 97 | XCTAssertEqual(e[0], "a", "Invalid value for split 1") 98 | XCTAssertEqual(e[1], "b", "Invalid value for split 2") 99 | XCTAssertEqual(e[2], "c-d", "Invalid value for last split") 100 | } 101 | 102 | func testPaddedByCharacter() { 103 | let a = "this is a test" 104 | 105 | XCTAssertEqual(a.paddedToWidth(80).characters.count, 106 | 80, "Failed to pad to correct width") 107 | XCTAssertEqual(a.paddedToWidth(5).characters.count, 108 | a.characters.count, "Bad padding when pad width is less than string width") 109 | XCTAssertEqual(a.paddedToWidth(-2).characters.count, 110 | a.characters.count, "Bad padding with negative pad width") 111 | 112 | let b = a.paddedToWidth(80) 113 | let lastBCharIndex = advance(b.endIndex, -1) 114 | XCTAssertEqual(b[lastBCharIndex], " " as Character, "Failed to pad with default character") 115 | 116 | let c = a.paddedToWidth(80, padBy: "+") 117 | let lastCCharIndex = advance(c.endIndex, -1) 118 | XCTAssertEqual(c[lastCCharIndex], "+" as Character, "Failed to pad with specified character") 119 | } 120 | 121 | func testWrappedAtWidth() { 122 | let lipsum = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 123 | for line in lipsum.wrappedAtWidth(80).splitByCharacter("\n") { 124 | XCTAssertLessThanOrEqual(line.characters.count, 80, "Failed to wrap long line: \(line)") 125 | } 126 | 127 | /* Words longer than the wrap width should not be split */ 128 | let longWords = "Lorem ipsum consectetur adipisicing eiusmod tempor incididunt" 129 | let lines = longWords.wrappedAtWidth(3).splitByCharacter("\n") 130 | XCTAssertEqual(lines.count, 8, "Failed to wrap long words") 131 | for line in lines { 132 | XCTAssertGreaterThan(line.characters.count, 3, "Bad long word wrapping on line: \(line)") 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /swift4/vendor/CommandLine/README.md: -------------------------------------------------------------------------------- 1 | CommandLine 2 | =========== 3 | A pure Swift library for creating command-line interfaces. 4 | 5 | *Note: CommandLine `master` requires Xcode 7 / Swift 2.0. If you're using older versions of Swift, please check out the [earlier releases](https://github.com/jatoben/CommandLine/releases).* 6 | 7 | Usage 8 | ----- 9 | CommandLine aims to have a simple and self-explanatory API. 10 | 11 | ```swift 12 | import CommandLine 13 | 14 | let cli = CommandLine() 15 | 16 | let filePath = StringOption(shortFlag: "f", longFlag: "file", required: true, 17 | helpMessage: "Path to the output file.") 18 | let compress = BoolOption(shortFlag: "c", longFlag: "compress", 19 | helpMessage: "Use data compression.") 20 | let help = BoolOption(shortFlag: "h", longFlag: "help", 21 | helpMessage: "Prints a help message.") 22 | let verbosity = CounterOption(shortFlag: "v", longFlag: "verbose", 23 | helpMessage: "Print verbose messages. Specify multiple times to increase verbosity.") 24 | 25 | cli.addOptions(filePath, compress, help, verbosity) 26 | 27 | do { 28 | try cli.parse() 29 | } catch { 30 | cli.printUsage(error) 31 | exit(EX_USAGE) 32 | } 33 | 34 | println("File path is \(filePath.value!)") 35 | println("Compress is \(compress.value)") 36 | println("Verbosity is \(verbosity.value)") 37 | ``` 38 | 39 | See `Option.swift` for additional Option types. 40 | 41 | To use CommandLine in your project, add it to your workspace, then add CommandLine.framework to the __Build Phases / Link Binary With Libraries__ setting of your target. 42 | 43 | If you are building a command-line tool and need to embed this and other frameworks to it, follow the steps in http://colemancda.github.io/programming/2015/02/12/embedded-swift-frameworks-osx-command-line-tools/ to link Swift frameworks to your command-line tool. 44 | 45 | If you are building a standalone command-line tool, you'll need to add the CommandLine source files directly to your target, because Xcode [can't yet build static libraries that contain Swift code](https://github.com/ksm/SwiftInFlux#static-libraries). 46 | 47 | 48 | Features 49 | -------- 50 | 51 | ### Automatically generated usage messages 52 | 53 | ``` 54 | Usage: example [options] 55 | -f, --file: 56 | Path to the output file. 57 | -c, --compress: 58 | Use data compression. 59 | -h, --help: 60 | Prints a help message. 61 | -v, --verbose: 62 | Print verbose messages. Specify multiple times to increase verbosity. 63 | ``` 64 | 65 | ### Supports all common flag styles 66 | 67 | These command-lines are equivalent: 68 | 69 | ```bash 70 | $ ./example -c -v -f /path/to/file 71 | $ ./example -cvf /path/to/file 72 | $ ./example -c --verbose --file /path/to/file 73 | $ ./example -cv --file /path/to/file 74 | $ ./example --compress -v --file=/path/to/file 75 | ``` 76 | 77 | Option processing can be stopped with '--', [as in getopt(3)](https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html). 78 | 79 | ### Intelligent handling of negative int & float arguments 80 | 81 | This will pass negative 42 to the int option, and negative 3.1419 to the float option: 82 | 83 | ```bash 84 | $ ./example2 -i -42 --float -3.1419 85 | ``` 86 | 87 | ### Locale-aware float parsing 88 | 89 | Floats will be handled correctly when in a locale that uses an alternate decimal point character: 90 | 91 | ```bash 92 | $ LC_NUMERIC=sv_SE.UTF-8 ./example2 --float 3,1419 93 | ``` 94 | 95 | ### Type-safe Enum options 96 | 97 | ```swift 98 | enum Operation: String { 99 | case Create = "c" 100 | case Extract = "x" 101 | case List = "l" 102 | case Verify = "v" 103 | } 104 | 105 | let cli = CommandLine() 106 | let op = EnumOption(shortFlag: "o", longFlag: "operation", required: true, 107 | helpMessage: "File operation - c for create, x for extract, l for list, or v for verify.") 108 | cli.setOptions(op) 109 | 110 | do { 111 | try cli.parse() 112 | } catch { 113 | cli.printUsage(error) 114 | exit(EX_USAGE) 115 | } 116 | 117 | switch op.value { 118 | case Operation.Create: 119 | // Create file 120 | 121 | case Operation.Extract: 122 | // Extract file 123 | 124 | // Remainder of cases 125 | } 126 | ``` 127 | 128 | Note: Enums must be initalizable from a String value. 129 | 130 | ### Fully emoji-compliant 131 | 132 | ```bash 133 | $ ./example3 -👍 --👻 134 | ``` 135 | 136 | *(please don't actually do this)* 137 | 138 | License 139 | ------- 140 | Copyright (c) 2014 Ben Gollmer. 141 | 142 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 143 | 144 | http://www.apache.org/licenses/LICENSE-2.0 145 | 146 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 147 | -------------------------------------------------------------------------------- /swift5/CocoaFob.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /swift5/CocoaFob.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /swift5/CocoaFob.xcodeproj/xcshareddata/xcschemes/CocoaFob.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /swift5/CocoaFob/CFUtil.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CFUtil.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 12/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func cfTry(_ err: CocoaFobError, cfBlock: (UnsafeMutablePointer?>) -> DarwinBoolean) throws { 12 | var cferr: Unmanaged? = nil 13 | if !cfBlock(&cferr).boolValue { 14 | if let cferr = cferr?.takeRetainedValue() { 15 | throw cferr 16 | } else { 17 | throw err 18 | } 19 | } 20 | } 21 | 22 | func cfTry(_ err: CocoaFobError, cfBlock: (UnsafeMutablePointer?>) -> T?) throws -> T { 23 | var cferr: Unmanaged? = nil 24 | if let result = cfBlock(&cferr) { 25 | return result 26 | } 27 | if let cferr = cferr?.takeRetainedValue() { 28 | throw cferr 29 | } else { 30 | throw err 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /swift5/CocoaFob/CocoaFob.h: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFob.h 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 05/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for CocoaFob. 12 | FOUNDATION_EXPORT double CocoaFobVersionNumber; 13 | 14 | //! Project version string for CocoaFob. 15 | FOUNDATION_EXPORT const unsigned char CocoaFobVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /swift5/CocoaFob/CocoaFobError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobError.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 05/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Custom error type: 13 | - Error: Unspecified error 14 | */ 15 | enum CocoaFobError: Error { 16 | case error 17 | } 18 | -------------------------------------------------------------------------------- /swift5/CocoaFob/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015 PixelEspresso. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /swift5/CocoaFob/LicenseGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobLicGenerator.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 05/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Generates CocoaFob registration keys 13 | */ 14 | public struct LicenseGenerator { 15 | 16 | var privKey: SecKey 17 | 18 | // MARK: - Initialization 19 | 20 | /** 21 | Initializes key generator with a private key in PEM format 22 | 23 | - parameter privateKeyPEM: String containing PEM representation of the private key 24 | */ 25 | public init?(privateKeyPEM: String) { 26 | let emptyString = "" as NSString 27 | let password = Unmanaged.passUnretained(emptyString as AnyObject) 28 | var params = SecItemImportExportKeyParameters( 29 | version: UInt32(bitPattern: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION), 30 | flags: SecKeyImportExportFlags.importOnlyOne, 31 | passphrase: password, 32 | alertTitle: Unmanaged.passUnretained(emptyString), 33 | alertPrompt: Unmanaged.passUnretained(emptyString), 34 | accessRef: nil, 35 | keyUsage: nil, 36 | keyAttributes: nil) 37 | var keyFormat = SecExternalFormat.formatPEMSequence 38 | var keyType = SecExternalItemType.itemTypePrivateKey 39 | guard let keyData = privateKeyPEM.data(using: String.Encoding.utf8) else { return nil } 40 | let keyBytes = [UInt8](keyData) 41 | let keyDataCF = CFDataCreate(nil, keyBytes, keyData.count)! 42 | var importArray: CFArray? = nil 43 | let osStatus = withUnsafeMutablePointer(to: &keyFormat, {pKeyFormat -> OSStatus in 44 | withUnsafeMutablePointer(to: &keyType, { pKeyType in 45 | SecItemImport(keyDataCF, nil, pKeyFormat, pKeyType, SecItemImportExportFlags(rawValue: 0), ¶ms, nil, &importArray) 46 | }) 47 | }) 48 | guard osStatus == errSecSuccess, importArray != nil else { return nil } 49 | let items = importArray! as NSArray 50 | guard items.count > 0 else { return nil } 51 | self.privKey = items[0] as! SecKey 52 | } 53 | 54 | // MARK: - Key generation 55 | 56 | /** 57 | Generates registration key for a user name 58 | 59 | - parameter userName: User name for which to generate a registration key 60 | - returns: Registration key 61 | */ 62 | public func generate(_ name: String) throws -> String { 63 | guard name != "" else { throw CocoaFobError.error } 64 | guard let nameData = getNameData(name) else { throw CocoaFobError.error } 65 | guard let signer = getSigner(nameData) else { throw CocoaFobError.error } 66 | guard let encoder = getEncoder() else { throw CocoaFobError.error } 67 | guard let group = connectTransforms(signer, encoder: encoder) else { throw CocoaFobError.error } 68 | let regDataCF = try cfTry(CocoaFobError.error) { return SecTransformExecute(group, $0) } 69 | guard let regData = regDataCF as? Data else { throw CocoaFobError.error } 70 | guard let reg = String(data: regData, encoding: .utf8) else { throw CocoaFobError.error } 71 | return reg.cocoaFobToReadableKey() 72 | } 73 | 74 | // MARK: - Utility functions 75 | 76 | fileprivate func connectTransforms(_ signer: SecTransform, encoder: SecTransform) -> SecGroupTransform? { 77 | guard let groupTransform = getGroupTransform() else { return nil } 78 | return SecTransformConnectTransforms(signer, kSecTransformOutputAttributeName, encoder, kSecTransformInputAttributeName, groupTransform, nil) 79 | } 80 | 81 | fileprivate func getGroupTransform() -> SecGroupTransform? { 82 | return SecTransformCreateGroupTransform() 83 | } 84 | 85 | func getNameData(_ name: String) -> Data? { 86 | return name.data(using: String.Encoding.utf8) 87 | } 88 | 89 | func getSigner(_ nameData: Data) -> SecTransform? { 90 | do { 91 | let signer = try cfTry(.error) { return SecSignTransformCreate(self.privKey, $0) } 92 | let _ = try cfTry(.error) { return SecTransformSetAttribute(signer, kSecTransformInputAttributeName, nameData as CFTypeRef, $0) } 93 | let _ = try cfTry(.error) { return SecTransformSetAttribute(signer, kSecDigestTypeAttribute, kSecDigestSHA1, $0) } 94 | return signer 95 | } catch { 96 | return nil 97 | } 98 | } 99 | 100 | fileprivate func getEncoder() -> SecTransform? { 101 | do { 102 | let encoder = try cfTry(.error) { return SecEncodeTransformCreate(kSecBase32Encoding, $0) } 103 | return encoder 104 | } catch { 105 | return nil 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /swift5/CocoaFob/LicenseVerifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobLicVerifier.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 12/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Verifies CocoaFob registration keys 13 | */ 14 | public struct LicenseVerifier { 15 | 16 | var pubKey: SecKey 17 | 18 | // MARK: - Initialization 19 | 20 | /** 21 | Initializes key verifier with a public key in PEM format 22 | 23 | - parameter publicKeyPEM: String containing PEM representation of the public key 24 | */ 25 | public init?(publicKeyPEM: String) { 26 | let emptyString = "" as NSString 27 | let password = Unmanaged.passUnretained(emptyString as AnyObject) 28 | var params = SecItemImportExportKeyParameters( 29 | version: UInt32(bitPattern: SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION), 30 | flags: SecKeyImportExportFlags.importOnlyOne, 31 | passphrase: password, 32 | alertTitle: Unmanaged.passUnretained(emptyString), 33 | alertPrompt: Unmanaged.passUnretained(emptyString), 34 | accessRef: nil, 35 | keyUsage: nil, 36 | keyAttributes: nil) 37 | var keyFormat = SecExternalFormat.formatPEMSequence 38 | var keyType = SecExternalItemType.itemTypePublicKey 39 | guard let keyData = publicKeyPEM.data(using: String.Encoding.utf8) else { return nil } 40 | let keyBytes = [UInt8](keyData) 41 | let keyDataCF = CFDataCreate(nil, keyBytes, keyData.count)! 42 | var importArray: CFArray? = nil 43 | let osStatus = withUnsafeMutablePointer(to: &keyFormat) { pKeyFormat in 44 | withUnsafeMutablePointer(to: &keyType, {pKeyType in 45 | SecItemImport(keyDataCF, nil, pKeyFormat, pKeyType, SecItemImportExportFlags(rawValue: 0), ¶ms, nil, &importArray) 46 | }) 47 | } 48 | guard osStatus == errSecSuccess, importArray != nil else { return nil } 49 | let items = importArray! as NSArray 50 | guard items.count > 0 else { return nil } 51 | self.pubKey = items[0] as! SecKey 52 | } 53 | 54 | /** 55 | Verifies registration key against registered name. Doesn't throw since you are most likely not interested in the reason registration verification failed. 56 | 57 | - parameter regKey: Registration key string 58 | - parameter name: Registered name string 59 | - returns: `true` if the registration key is valid for the given name, `false` if not 60 | */ 61 | public func verify(_ regKey: String, forName name: String) -> Bool { 62 | do { 63 | let keyString = regKey.cocoaFobFromReadableKey() 64 | guard let keyData = keyString.data(using: String.Encoding.utf8) else { return false } 65 | guard let nameData = name.data(using: String.Encoding.utf8) else { return false } 66 | let decoder = try getDecoder(keyData) 67 | let signature = try cfTry(.error) { SecTransformExecute(decoder, $0) } 68 | 69 | // reverse the signature to check for truncated data / additional data entered by the user 70 | let encoder = try getEncoder(signature as! Data) 71 | let reverseSignature = try cfTry(.error) { SecTransformExecute(encoder, $0) } 72 | let reverseSignatureString = String(decoding: reverseSignature as! Data, as: UTF8.self).replacingOccurrences(of: "=", with: "") 73 | if(reverseSignatureString != keyString) { return false } 74 | 75 | let verifier = try getVerifier(self.pubKey, signature: signature as! Data, nameData: nameData) 76 | let result = try cfTry(.error) { SecTransformExecute(verifier, $0) } 77 | let boolResult = result as! CFBoolean 78 | return Bool(truncating: boolResult) 79 | } catch { 80 | return false 81 | } 82 | } 83 | 84 | // MARK: - Helper functions 85 | fileprivate func getEncoder(_ signature: Data) throws -> SecTransform { 86 | let encoder = try cfTry(.error) { return SecEncodeTransformCreate(kSecBase32Encoding, $0) } 87 | let _ = try cfTry(.error) { return SecTransformSetAttribute(encoder, kSecTransformInputAttributeName, signature as CFTypeRef, $0) } 88 | return encoder 89 | } 90 | 91 | fileprivate func getDecoder(_ keyData: Data) throws -> SecTransform { 92 | let decoder = try cfTry(.error) { return SecDecodeTransformCreate(kSecBase32Encoding, $0) } 93 | let _ = try cfTry(.error) { return SecTransformSetAttribute(decoder, kSecTransformInputAttributeName, keyData as CFTypeRef, $0) } 94 | return decoder 95 | } 96 | 97 | fileprivate func getVerifier(_ publicKey: SecKey, signature: Data, nameData: Data) throws -> SecTransform { 98 | let verifier = try cfTry(.error) { return SecVerifyTransformCreate(publicKey, signature as CFData?, $0) } 99 | let _ = try cfTry(.error) { return SecTransformSetAttribute(verifier, kSecTransformInputAttributeName, nameData as CFTypeRef, $0) } 100 | let _ = try cfTry(.error) { return SecTransformSetAttribute(verifier, kSecDigestTypeAttribute, kSecDigestSHA1, $0) } 101 | return verifier 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /swift5/CocoaFob/String+CocoaFob.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CocoaFobStringExt.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 12/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | /** 14 | Converts generated CocoaFob key to its human-readable form: 15 | - replaces Os with 8s and Is with 9s 16 | - trims padding characters at the end 17 | - inserts dashes every 5 characters 18 | 19 | - returns: Human-readable registration key 20 | */ 21 | func cocoaFobToReadableKey() -> String { 22 | let replacedOwith8 = self.replacingOccurrences(of: "O", with: "8") 23 | let replacedIwith9 = replacedOwith8.replacingOccurrences(of: "I", with: "9") 24 | var key = replacedIwith9.replacingOccurrences(of: "=", with: "") 25 | 26 | var index = 5 27 | while index < key.utf8.count { 28 | key.insert(contentsOf: ["-"], at: key.index(key.startIndex, offsetBy: index)) 29 | index += 6 30 | } 31 | 32 | return key 33 | } 34 | 35 | /** 36 | Reverses readability changes to a supplied key: 37 | - removes dashes 38 | - replaces 9s with Is and 8s with Os 39 | 40 | - returns: Compacted key ready for verification 41 | */ 42 | func cocoaFobFromReadableKey() -> String { 43 | let replaced9withI = self.replacingOccurrences(of: "9", with: "I") 44 | let replaced8withO = replaced9withI.replacingOccurrences(of: "8", with: "O") 45 | let key = replaced8withO.replacingOccurrences(of: "-", with: "") 46 | return key 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /swift5/CocoaFobTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /swift5/README.md: -------------------------------------------------------------------------------- 1 | # CocoaFob Swift 5.0 Port 2 | 3 | For a Swift 4-based version, see https://github.com/glebd/cocoafob/tree/master/swift4 4 | 5 | ## Instructions 6 | 7 | Add the necessary files directly to your project. For your app they are: 8 | 9 | * `CocoaFob/CFUtil.swift` 10 | * `CocoaFob/CocoaFobError.swift` 11 | * `CocoaFob/CocoaFobLicVerifier.swift` 12 | * `CocoaFob/CocoaFobStringExt.swift` 13 | 14 | Generate your DSA key as described in the main README and add the public key as a resource to your app. 15 | 16 | Look at the tests in `CocoaFobTests/CocoaFobTests.swift` to see how to verify a registration key. 17 | 18 | ## Build 19 | 20 | To build and install the `cocoafob-keygen` command-line utility for generating and verifying registration keys, execute the following command in the `swift` subdirectory of the CocoaFob project: 21 | 22 | ```bash 23 | xcodebuild -target cocoafob-keygen install 24 | ``` 25 | 26 | The utility will be installed in `/usr/local/bin`. 27 | 28 | ## Dependencies 29 | 30 | * CommandLine by Ben Gollmer, GitHub -- https://github.com/jatoben/CommandLine 31 | 32 | ```bash 33 | git subtree {add|pull} --squash --prefix swift/vendor/CommandLine git://github.com/jatoben/CommandLine.git master 34 | ``` 35 | -------------------------------------------------------------------------------- /swift5/cocoafob-keygen/Functions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Functions.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 16/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func verifyRegKey(pubKeyPath: String, userName: String, regKey: String) throws -> Bool { 12 | let pubKeyPEM = try String(contentsOfFile: pubKeyPath, encoding: .utf8) 13 | if let verifier = LicenseVerifier(publicKeyPEM: pubKeyPEM) { 14 | return verifier.verify(regKey, forName: userName) 15 | } 16 | return false 17 | } 18 | 19 | func generateRegKey(pvtKeyPath: String, userName: String) throws -> String { 20 | let pvtKeyPEM = try String(contentsOfFile: pvtKeyPath, encoding: .utf8) 21 | if let generator = LicenseGenerator(privateKeyPEM: pvtKeyPEM) { 22 | return try generator.generate(userName) 23 | } 24 | throw CocoaFobError.error 25 | } 26 | 27 | func generateRegURL(pvtKeyPath: String, userName: String, schema: String) throws -> String? { 28 | let regKey = try generateRegKey(pvtKeyPath: pvtKeyPath, userName: userName) 29 | if let userNameData = userName.data(using: .utf8) { 30 | let userNameBase64 = userNameData.base64EncodedData(options: .init(rawValue: 0)) 31 | return "\(schema)://\(userNameBase64)/\(regKey)" 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /swift5/cocoafob-keygen/Stderr.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StderrOutputStream.swift 3 | // CocoaFob 4 | // 5 | // Created by Gleb Dolgich on 16/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // From http://ericasadun.com/2015/06/09/swift-2-0-how-to-print/ 12 | public struct Stderr: TextOutputStream { 13 | public mutating func write(_ string: String) { 14 | fputs(string, stderr) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /swift5/cocoafob-keygen/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // cocoafob-keygen 4 | // 5 | // Created by Gleb Dolgich on 14/07/2015. 6 | // Copyright © 2015 PixelEspresso. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let pubKey = StringOption(longFlag: "pubkey", helpMessage: "Public DSA key file path to verify registration") 12 | let pvtKey = StringOption(longFlag: "pvtkey", helpMessage: "Private DSA key file path to generate registration") 13 | let userName = StringOption(shortFlag: "u", longFlag: "username", helpMessage: "User name") 14 | let regKey = StringOption(shortFlag: "r", longFlag: "regkey", helpMessage: "Registration key to verify") 15 | let urlSchema = StringOption(longFlag: "schema", helpMessage: "URL schema for registration links (if given a URL will be generated)") 16 | let help = BoolOption(shortFlag: "h", longFlag: "help", helpMessage: "Print help message") 17 | 18 | print("CocoaFob Key Generator, Version 1.0 -- Copyright (C) 2015 PixelEspresso") 19 | 20 | let cli = CommandLine() 21 | cli.addOptions(pubKey, pvtKey, userName, regKey, urlSchema, help) 22 | 23 | var errStream = Stderr() 24 | 25 | do { 26 | try cli.parse() 27 | } catch { 28 | cli.printUsage(error) 29 | exit(EX_USAGE) 30 | } 31 | 32 | if let pub = pubKey.value { 33 | 34 | if let user = userName.value, let reg = regKey.value { 35 | do { 36 | print("Name: \(user)\n Key: \(reg)") 37 | if try verifyRegKey(pubKeyPath: pub, userName: user, regKey: reg) { 38 | print("Registration is VALID") 39 | } else { 40 | print("Registration is INVALID") 41 | exit(1) 42 | } 43 | } catch { 44 | print("ERROR: Unable to verify registration key -- \(error)", errStream) 45 | exit(EX_DATAERR) 46 | } 47 | } else { 48 | print("ERROR: Specifying a public key means 'verify' and requires both user name and registration key", errStream) 49 | cli.printUsage() 50 | exit(EX_USAGE) 51 | } 52 | 53 | } else if let pvt = pvtKey.value { 54 | 55 | if let user = userName.value { 56 | if regKey.value != nil { 57 | print("WARNING: Specifying a private key means 'generate' and doesn't need a registration key", errStream) 58 | } 59 | do { 60 | if let schema = urlSchema.value { 61 | if let url = try generateRegURL(pvtKeyPath: pvt, userName: user, schema: schema) { 62 | print(url) 63 | } else { 64 | print("ERROR: Unable to generate registration URL") 65 | } 66 | } else { 67 | let reg = try generateRegKey(pvtKeyPath: pvt, userName: user) 68 | print("Name: \(user)\n Key: \(reg)") 69 | } 70 | } catch { 71 | print("ERROR: Unable to generate registration key -- \(error)", errStream) 72 | exit(EX_DATAERR) 73 | } 74 | } else { 75 | print("ERROR: Specifying a private key means 'verify' and requires a user name", errStream) 76 | cli.printUsage() 77 | exit(EX_USAGE) 78 | } 79 | 80 | } else { 81 | 82 | if help.value { 83 | cli.printUsage() 84 | } else { 85 | print("ERROR: Either private or public key must be provided", errStream) 86 | cli.printUsage() 87 | exit(EX_USAGE) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /swift5/vendor/CommandLine/.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | -------------------------------------------------------------------------------- /swift5/vendor/CommandLine/.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | xcode_project: CommandLine.xcodeproj 3 | xcode_scheme: CommandLine 4 | script: xcodebuild -scheme CommandLine test 5 | osx_image: beta-xcode6.3 6 | 7 | # Disable all builds until Travis supports Swift 2.0 8 | branches: 9 | only: [] 10 | -------------------------------------------------------------------------------- /swift5/vendor/CommandLine/CommandLine.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /swift5/vendor/CommandLine/CommandLine/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSHumanReadableCopyright 24 | Copyright © 2014 Ben Gollmer. Licensed under the Apache License, Version 2.0. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /swift5/vendor/CommandLine/CommandLine/Option.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Option.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /** 19 | * The base class for a command-line option. 20 | */ 21 | public class Option { 22 | public let shortFlag: String? 23 | public let longFlag: String? 24 | public let required: Bool 25 | public let helpMessage: String 26 | 27 | /** True if the option was set when parsing command-line arguments */ 28 | public var wasSet: Bool { 29 | return false 30 | } 31 | 32 | public var claimedValues: Int { return 0 } 33 | 34 | public var flagDescription: String { 35 | switch (shortFlag, longFlag) { 36 | case let (sf?, lf?): 37 | return "\(shortOptionPrefix)\(sf), \(longOptionPrefix)\(lf)" 38 | case (nil, let lf?): 39 | return "\(longOptionPrefix)\(lf)" 40 | case (let sf?, nil): 41 | return "\(shortOptionPrefix)\(sf)" 42 | default: 43 | return "" 44 | } 45 | } 46 | 47 | internal init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) { 48 | if let sf = shortFlag { 49 | assert(sf.count == 1, "Short flag must be a single character") 50 | assert(Int(sf) == nil && sf.toDouble() == nil, "Short flag cannot be a numeric value") 51 | } 52 | 53 | if let lf = longFlag { 54 | assert(Int(lf) == nil && lf.toDouble() == nil, "Long flag cannot be a numeric value") 55 | } 56 | 57 | self.shortFlag = shortFlag 58 | self.longFlag = longFlag 59 | self.helpMessage = helpMessage 60 | self.required = required 61 | } 62 | 63 | /* The optional casts in these initalizers force them to call the private initializer. Without 64 | * the casts, they recursively call themselves. 65 | */ 66 | 67 | /** Initializes a new Option that has both long and short flags. */ 68 | public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) { 69 | self.init(shortFlag as String?, longFlag, required, helpMessage) 70 | } 71 | 72 | /** Initializes a new Option that has only a short flag. */ 73 | public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) { 74 | self.init(shortFlag as String?, nil, required, helpMessage) 75 | } 76 | 77 | /** Initializes a new Option that has only a long flag. */ 78 | public convenience init(longFlag: String, required: Bool = false, helpMessage: String) { 79 | self.init(nil, longFlag as String?, required, helpMessage) 80 | } 81 | 82 | func flagMatch(_ flag: String) -> Bool { 83 | return flag == shortFlag || flag == longFlag 84 | } 85 | 86 | func setValue(_ values: [String]) -> Bool { 87 | return false 88 | } 89 | } 90 | 91 | /** 92 | * A boolean option. The presence of either the short or long flag will set the value to true; 93 | * absence of the flag(s) is equivalent to false. 94 | */ 95 | public class BoolOption: Option { 96 | private var _value: Bool = false 97 | 98 | public var value: Bool { 99 | return _value 100 | } 101 | 102 | override public var wasSet: Bool { 103 | return _value 104 | } 105 | 106 | override func setValue(_ values: [String]) -> Bool { 107 | _value = true 108 | return true 109 | } 110 | } 111 | 112 | /** An option that accepts a string value. */ 113 | public class StringOption: Option { 114 | private var _value: String? = nil 115 | 116 | public var value: String? { 117 | return _value 118 | } 119 | 120 | override public var wasSet: Bool { 121 | return _value != nil 122 | } 123 | 124 | override public var claimedValues: Int { 125 | return _value != nil ? 1 : 0 126 | } 127 | 128 | override func setValue(_ values: [String]) -> Bool { 129 | if values.count == 0 { 130 | return false 131 | } 132 | 133 | _value = values[0] 134 | return true 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /swift5/vendor/CommandLine/CommandLine/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * StringExtensions.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* Required for localeconv(3) */ 19 | #if os(OSX) 20 | import Darwin 21 | #elseif os(Linux) 22 | import Glibc 23 | #endif 24 | 25 | internal extension String { 26 | /* Retrieves locale-specified decimal separator from the environment 27 | * using localeconv(3). 28 | */ 29 | private func _localDecimalPoint() -> Character { 30 | guard let locale = localeconv(), let decimalPoint = locale.pointee.decimal_point else { 31 | return "." 32 | } 33 | 34 | return Character(UnicodeScalar(UInt8(bitPattern: decimalPoint.pointee))) 35 | } 36 | 37 | /** 38 | * Attempts to parse the string value into a Double. 39 | * 40 | * - returns: A Double if the string can be parsed, nil otherwise. 41 | */ 42 | func toDouble() -> Double? { 43 | let decimalPoint = String(self._localDecimalPoint()) 44 | guard decimalPoint == "." || self.range(of: ".") == nil else { return nil } 45 | let localeSelf = self.replacingOccurrences(of: decimalPoint, with: ".") 46 | return Double(localeSelf) 47 | } 48 | 49 | /** 50 | * Pads a string to the specified width. 51 | * 52 | * - parameter toWidth: The width to pad the string to. 53 | * - parameter by: The character to use for padding. 54 | * 55 | * - returns: A new string, padded to the given width. 56 | */ 57 | func padded(toWidth width: Int, with padChar: Character = " ") -> String { 58 | var s = self 59 | 60 | while s.count < width { 61 | s.append(padChar) 62 | } 63 | 64 | return s 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /swift5/vendor/CommandLine/CommandLineTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2014 Ben Gollmer. Licensed under the Apache License, Version 2.0. 25 | 26 | 27 | -------------------------------------------------------------------------------- /swift5/vendor/CommandLine/CommandLineTests/StringExtensionTests.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * StringExtensionTests.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import XCTest 19 | import CommandLine 20 | 21 | class StringExtensionTests: XCTestCase { 22 | 23 | func testToDouble() { 24 | /* Regular ol' double */ 25 | let a = "3.14159".toDouble() 26 | XCTAssertEqual(a!, 3.14159, "Failed to parse pi as double") 27 | 28 | let b = "-98.23".toDouble() 29 | XCTAssertEqual(b!, -98.23, "Failed to parse negative double") 30 | 31 | /* Ints should be parsable as doubles */ 32 | let c = "5".toDouble() 33 | XCTAssertEqual(c!, 5, "Failed to parse int as double") 34 | 35 | let d = "-2099".toDouble() 36 | XCTAssertEqual(d!, -2099, "Failed to parse negative int as double") 37 | 38 | 39 | /* Zero handling */ 40 | let e = "0.0".toDouble() 41 | XCTAssertEqual(e!, 0, "Failed to parse zero double") 42 | 43 | let f = "0".toDouble() 44 | XCTAssertEqual(f!, 0, "Failed to parse zero int as double") 45 | 46 | let g = "0.0000000000000000".toDouble() 47 | XCTAssertEqual(g!, 0, "Failed to parse very long zero double") 48 | 49 | let h = "-0.0".toDouble() 50 | XCTAssertEqual(h!, 0, "Failed to parse negative zero double") 51 | 52 | let i = "-0".toDouble() 53 | XCTAssertEqual(i!, 0, "Failed to parse negative zero int as double") 54 | 55 | let j = "-0.000000000000000".toDouble() 56 | XCTAssertEqual(j!, 0, "Failed to parse very long negative zero double") 57 | 58 | 59 | /* Various extraneous chars */ 60 | let k = "+42.3".toDouble() 61 | XCTAssertNil(k, "Parsed double with extraneous +") 62 | 63 | let l = " 827.2".toDouble() 64 | XCTAssertNil(l, "Parsed double with extraneous space") 65 | 66 | let m = "283_3".toDouble() 67 | XCTAssertNil(m, "Parsed double with extraneous _") 68 | 69 | let n = "💩".toDouble() 70 | XCTAssertNil(n, "Parsed poo") 71 | 72 | /* Locale handling */ 73 | setlocale(LC_NUMERIC, "sv_SE.UTF-8") 74 | 75 | let o = "888,8".toDouble() 76 | XCTAssertEqual(o!, 888.8, "Failed to parse double in alternate locale") 77 | 78 | let p = "888.8".toDouble() 79 | XCTAssertNil(p, "Parsed double in alternate locale with wrong decimal point") 80 | 81 | /* Set locale back so as not to perturb any other tests */ 82 | setlocale(LC_NUMERIC, "") 83 | } 84 | 85 | func testSplitByCharacter() { 86 | let a = "1,2,3".splitByCharacter(",") 87 | XCTAssertEqual(a.count, 3, "Failed to split into correct number of components") 88 | 89 | let b = "123".splitByCharacter(",") 90 | XCTAssertEqual(b.count, 1, "Failed to split when separator not found") 91 | 92 | let c = "".splitByCharacter(",") 93 | XCTAssertEqual(c.count, 0, "Splitting empty string should return empty array") 94 | 95 | let e = "a-b-c-d".splitByCharacter("-", maxSplits: 2) 96 | XCTAssertEqual(e.count, 3, "Failed to limit splits") 97 | XCTAssertEqual(e[0], "a", "Invalid value for split 1") 98 | XCTAssertEqual(e[1], "b", "Invalid value for split 2") 99 | XCTAssertEqual(e[2], "c-d", "Invalid value for last split") 100 | } 101 | 102 | func testPaddedByCharacter() { 103 | let a = "this is a test" 104 | 105 | XCTAssertEqual(a.paddedToWidth(80).characters.count, 106 | 80, "Failed to pad to correct width") 107 | XCTAssertEqual(a.paddedToWidth(5).characters.count, 108 | a.characters.count, "Bad padding when pad width is less than string width") 109 | XCTAssertEqual(a.paddedToWidth(-2).characters.count, 110 | a.characters.count, "Bad padding with negative pad width") 111 | 112 | let b = a.paddedToWidth(80) 113 | let lastBCharIndex = advance(b.endIndex, -1) 114 | XCTAssertEqual(b[lastBCharIndex], " " as Character, "Failed to pad with default character") 115 | 116 | let c = a.paddedToWidth(80, padBy: "+") 117 | let lastCCharIndex = advance(c.endIndex, -1) 118 | XCTAssertEqual(c[lastCCharIndex], "+" as Character, "Failed to pad with specified character") 119 | } 120 | 121 | func testWrappedAtWidth() { 122 | let lipsum = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 123 | for line in lipsum.wrappedAtWidth(80).splitByCharacter("\n") { 124 | XCTAssertLessThanOrEqual(line.characters.count, 80, "Failed to wrap long line: \(line)") 125 | } 126 | 127 | /* Words longer than the wrap width should not be split */ 128 | let longWords = "Lorem ipsum consectetur adipisicing eiusmod tempor incididunt" 129 | let lines = longWords.wrappedAtWidth(3).splitByCharacter("\n") 130 | XCTAssertEqual(lines.count, 8, "Failed to wrap long words") 131 | for line in lines { 132 | XCTAssertGreaterThan(line.characters.count, 3, "Bad long word wrapping on line: \(line)") 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /swift5/vendor/CommandLine/README.md: -------------------------------------------------------------------------------- 1 | CommandLine 2 | =========== 3 | A pure Swift library for creating command-line interfaces. 4 | 5 | *Note: CommandLine `master` requires Xcode 7 / Swift 2.0. If you're using older versions of Swift, please check out the [earlier releases](https://github.com/jatoben/CommandLine/releases).* 6 | 7 | Usage 8 | ----- 9 | CommandLine aims to have a simple and self-explanatory API. 10 | 11 | ```swift 12 | import CommandLine 13 | 14 | let cli = CommandLine() 15 | 16 | let filePath = StringOption(shortFlag: "f", longFlag: "file", required: true, 17 | helpMessage: "Path to the output file.") 18 | let compress = BoolOption(shortFlag: "c", longFlag: "compress", 19 | helpMessage: "Use data compression.") 20 | let help = BoolOption(shortFlag: "h", longFlag: "help", 21 | helpMessage: "Prints a help message.") 22 | let verbosity = CounterOption(shortFlag: "v", longFlag: "verbose", 23 | helpMessage: "Print verbose messages. Specify multiple times to increase verbosity.") 24 | 25 | cli.addOptions(filePath, compress, help, verbosity) 26 | 27 | do { 28 | try cli.parse() 29 | } catch { 30 | cli.printUsage(error) 31 | exit(EX_USAGE) 32 | } 33 | 34 | println("File path is \(filePath.value!)") 35 | println("Compress is \(compress.value)") 36 | println("Verbosity is \(verbosity.value)") 37 | ``` 38 | 39 | See `Option.swift` for additional Option types. 40 | 41 | To use CommandLine in your project, add it to your workspace, then add CommandLine.framework to the __Build Phases / Link Binary With Libraries__ setting of your target. 42 | 43 | If you are building a command-line tool and need to embed this and other frameworks to it, follow the steps in http://colemancda.github.io/programming/2015/02/12/embedded-swift-frameworks-osx-command-line-tools/ to link Swift frameworks to your command-line tool. 44 | 45 | If you are building a standalone command-line tool, you'll need to add the CommandLine source files directly to your target, because Xcode [can't yet build static libraries that contain Swift code](https://github.com/ksm/SwiftInFlux#static-libraries). 46 | 47 | 48 | Features 49 | -------- 50 | 51 | ### Automatically generated usage messages 52 | 53 | ``` 54 | Usage: example [options] 55 | -f, --file: 56 | Path to the output file. 57 | -c, --compress: 58 | Use data compression. 59 | -h, --help: 60 | Prints a help message. 61 | -v, --verbose: 62 | Print verbose messages. Specify multiple times to increase verbosity. 63 | ``` 64 | 65 | ### Supports all common flag styles 66 | 67 | These command-lines are equivalent: 68 | 69 | ```bash 70 | $ ./example -c -v -f /path/to/file 71 | $ ./example -cvf /path/to/file 72 | $ ./example -c --verbose --file /path/to/file 73 | $ ./example -cv --file /path/to/file 74 | $ ./example --compress -v --file=/path/to/file 75 | ``` 76 | 77 | Option processing can be stopped with '--', [as in getopt(3)](https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html). 78 | 79 | ### Intelligent handling of negative int & float arguments 80 | 81 | This will pass negative 42 to the int option, and negative 3.1419 to the float option: 82 | 83 | ```bash 84 | $ ./example2 -i -42 --float -3.1419 85 | ``` 86 | 87 | ### Locale-aware float parsing 88 | 89 | Floats will be handled correctly when in a locale that uses an alternate decimal point character: 90 | 91 | ```bash 92 | $ LC_NUMERIC=sv_SE.UTF-8 ./example2 --float 3,1419 93 | ``` 94 | 95 | ### Type-safe Enum options 96 | 97 | ```swift 98 | enum Operation: String { 99 | case Create = "c" 100 | case Extract = "x" 101 | case List = "l" 102 | case Verify = "v" 103 | } 104 | 105 | let cli = CommandLine() 106 | let op = EnumOption(shortFlag: "o", longFlag: "operation", required: true, 107 | helpMessage: "File operation - c for create, x for extract, l for list, or v for verify.") 108 | cli.setOptions(op) 109 | 110 | do { 111 | try cli.parse() 112 | } catch { 113 | cli.printUsage(error) 114 | exit(EX_USAGE) 115 | } 116 | 117 | switch op.value { 118 | case Operation.Create: 119 | // Create file 120 | 121 | case Operation.Extract: 122 | // Extract file 123 | 124 | // Remainder of cases 125 | } 126 | ``` 127 | 128 | Note: Enums must be initalizable from a String value. 129 | 130 | ### Fully emoji-compliant 131 | 132 | ```bash 133 | $ ./example3 -👍 --👻 134 | ``` 135 | 136 | *(please don't actually do this)* 137 | 138 | License 139 | ------- 140 | Copyright (c) 2014 Ben Gollmer. 141 | 142 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 143 | 144 | http://www.apache.org/licenses/LICENSE-2.0 145 | 146 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 147 | --------------------------------------------------------------------------------