diff --git a/Developer/AquaticPrime Developer.xcodeproj/project.pbxproj b/Developer/AquaticPrime Developer.xcodeproj/project.pbxproj new file mode 100644 index 0000000..7a0a3ba --- /dev/null +++ b/Developer/AquaticPrime Developer.xcodeproj/project.pbxproj @@ -0,0 +1,538 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 3D12D14B085206240035BA78 /* add.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 3D12D14A085206240035BA78 /* add.tiff */; }; + 3D12D159085208CC0035BA78 /* StatusController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D12D158085208CC0035BA78 /* StatusController.m */; }; + 3D12D1C008520D0E0035BA78 /* ProductController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D12D1BF08520D0E0035BA78 /* ProductController.m */; }; + 3D160600089D3DBC00526ED8 /* OAGradientTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D1605FE089D3DBC00526ED8 /* OAGradientTableView.m */; }; + 3D1606A4089D41C100526ED8 /* AQTextFieldCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D1606A2089D41C100526ED8 /* AQTextFieldCell.m */; }; + 3D4458EB08971CEC002901EB /* aquatic.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3D4458EA08971CEC002901EB /* aquatic.icns */; }; + 3D9364030856724F006F1864 /* LicenseController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D9364020856724F006F1864 /* LicenseController.m */; }; + 3DA8074C08A694CF006C1C9F /* badge.icns in Resources */ = {isa = PBXBuildFile; fileRef = 3DA8074B08A694CE006C1C9F /* badge.icns */; }; + 3DB12CA108803F0300160861 /* MainController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB12C9F08803F0300160861 /* MainController.m */; }; + 3DB12CFC0880423A00160861 /* LicenseInfo.nib in Resources */ = {isa = PBXBuildFile; fileRef = 3DB12CFB0880423A00160861 /* LicenseInfo.nib */; }; + 3DB12D080880427F00160861 /* InfoController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DB12D060880427F00160861 /* InfoController.m */; }; + 3DC1B057088183B10007C7E0 /* aboutbox.png in Resources */ = {isa = PBXBuildFile; fileRef = 3DC1B056088183B10007C7E0 /* aboutbox.png */; }; + 3DD108C0088170A100C4C6B4 /* AQAboutView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DD108BA088170A100C4C6B4 /* AQAboutView.m */; }; + 3DD108C2088170A100C4C6B4 /* AQAboutWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DD108BC088170A100C4C6B4 /* AQAboutWindow.m */; }; + 3DD108C4088170A100C4C6B4 /* AQTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DD108BE088170A100C4C6B4 /* AQTableView.m */; }; + 3DD108F40881725A00C4C6B4 /* About.nib in Resources */ = {isa = PBXBuildFile; fileRef = 3DD108F30881725A00C4C6B4 /* About.nib */; }; + 3DD108F80881726E00C4C6B4 /* AboutController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DD108F60881726E00C4C6B4 /* AboutController.m */; }; + 3DF60B9E085CE0BD004C50F2 /* remove.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 3DF60B9D085CE0BD004C50F2 /* remove.tiff */; }; + 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; }; + 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; + 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; + A0DA7A7509B3B3AA00673D39 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A0DA7A7409B3B3AA00673D39 /* Cocoa.framework */; }; + E804BCA70833AAFD00512005 /* KeyController.m in Sources */ = {isa = PBXBuildFile; fileRef = E804BCA60833AAFD00512005 /* KeyController.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + A0BFCA2509A3F240005C04A5 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = ""; }; + 32CA4F630368D1EE00C91783 /* AquaticPrimeDeveloper_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AquaticPrimeDeveloper_Prefix.pch; sourceTree = ""; }; + 3D12D14A085206240035BA78 /* add.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = add.tiff; sourceTree = ""; }; + 3D12D157085208CC0035BA78 /* StatusController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = StatusController.h; sourceTree = ""; }; + 3D12D158085208CC0035BA78 /* StatusController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = StatusController.m; sourceTree = ""; }; + 3D12D1BE08520D0E0035BA78 /* ProductController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ProductController.h; sourceTree = ""; }; + 3D12D1BF08520D0E0035BA78 /* ProductController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ProductController.m; sourceTree = ""; }; + 3D1605FE089D3DBC00526ED8 /* OAGradientTableView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = OAGradientTableView.m; sourceTree = ""; }; + 3D1605FF089D3DBC00526ED8 /* OAGradientTableView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OAGradientTableView.h; sourceTree = ""; }; + 3D1606A2089D41C100526ED8 /* AQTextFieldCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AQTextFieldCell.m; sourceTree = ""; }; + 3D1606A3089D41C100526ED8 /* AQTextFieldCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AQTextFieldCell.h; sourceTree = ""; }; + 3D4458EA08971CEC002901EB /* aquatic.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = aquatic.icns; path = Images/aquatic.icns; sourceTree = ""; }; + 3D6CDECF08F2B07500F1940E /* AquaticPrime.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AquaticPrime.h; sourceTree = ""; }; + 3D9364010856724F006F1864 /* LicenseController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = LicenseController.h; sourceTree = ""; }; + 3D9364020856724F006F1864 /* LicenseController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = LicenseController.m; sourceTree = ""; }; + 3DA8074B08A694CE006C1C9F /* badge.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = badge.icns; path = Images/badge.icns; sourceTree = ""; }; + 3DB12C9E08803F0300160861 /* MainController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = MainController.h; sourceTree = ""; }; + 3DB12C9F08803F0300160861 /* MainController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = MainController.m; sourceTree = ""; }; + 3DB12CFB0880423A00160861 /* LicenseInfo.nib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = LicenseInfo.nib; path = Nibs/LicenseInfo.nib; sourceTree = ""; }; + 3DB12D050880427F00160861 /* InfoController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = InfoController.h; sourceTree = ""; }; + 3DB12D060880427F00160861 /* InfoController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = InfoController.m; sourceTree = ""; }; + 3DC1B056088183B10007C7E0 /* aboutbox.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = aboutbox.png; sourceTree = ""; }; + 3DD108B9088170A100C4C6B4 /* AQAboutView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AQAboutView.h; sourceTree = ""; }; + 3DD108BA088170A100C4C6B4 /* AQAboutView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AQAboutView.m; sourceTree = ""; }; + 3DD108BB088170A100C4C6B4 /* AQAboutWindow.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AQAboutWindow.h; sourceTree = ""; }; + 3DD108BC088170A100C4C6B4 /* AQAboutWindow.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AQAboutWindow.m; sourceTree = ""; }; + 3DD108BD088170A100C4C6B4 /* AQTableView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AQTableView.h; sourceTree = ""; }; + 3DD108BE088170A100C4C6B4 /* AQTableView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AQTableView.m; sourceTree = ""; }; + 3DD108F30881725A00C4C6B4 /* About.nib */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = About.nib; path = Nibs/About.nib; sourceTree = ""; }; + 3DD108F50881726E00C4C6B4 /* AboutController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AboutController.h; sourceTree = ""; }; + 3DD108F60881726E00C4C6B4 /* AboutController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AboutController.m; sourceTree = ""; }; + 3DF60B9D085CE0BD004C50F2 /* remove.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = remove.tiff; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 8D1107320486CEB800E47090 /* AquaticPrime Developer.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "AquaticPrime Developer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + A0DA7A7409B3B3AA00673D39 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; + E804BCA50833AAFD00512005 /* KeyController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = KeyController.h; sourceTree = ""; }; + E804BCA60833AAFD00512005 /* KeyController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = KeyController.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8D11072E0486CEB800E47090 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A0DA7A7509B3B3AA00673D39 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 3D6CDECF08F2B07500F1940E /* AquaticPrime.h */, + 3DB12D8E088046A600160861 /* Main Controller */, + 3DB12C2808803BE900160861 /* Key Controller */, + 3DB12C2708803BDC00160861 /* Product Controller */, + 3DB12C2608803BD300160861 /* License Creation */, + 3DB12C2508803BBC00160861 /* License Info */, + 3DB12C2908803BF100160861 /* Status */, + 3DD108FA0881727400C4C6B4 /* About */, + 3DD108B8088170A100C4C6B4 /* AQAppKit */, + ); + path = Classes; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8D1107320486CEB800E47090 /* AquaticPrime Developer.app */, + ); + name = Products; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* AquaticPrimeDeveloper */ = { + isa = PBXGroup; + children = ( + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 3D12D149085206240035BA78 /* Images */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = AquaticPrimeDeveloper; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 32CA4F630368D1EE00C91783 /* AquaticPrimeDeveloper_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 3DA8074B08A694CE006C1C9F /* badge.icns */, + 3D4458EA08971CEC002901EB /* aquatic.icns */, + 8D1107310486CEB800E47090 /* Info.plist */, + 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, + 3DB12D280880431600160861 /* Nibs */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + A0DA7A7409B3B3AA00673D39 /* Cocoa.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 3D12D149085206240035BA78 /* Images */ = { + isa = PBXGroup; + children = ( + 3DC1B056088183B10007C7E0 /* aboutbox.png */, + 3D12D14A085206240035BA78 /* add.tiff */, + 3DF60B9D085CE0BD004C50F2 /* remove.tiff */, + ); + path = Images; + sourceTree = ""; + }; + 3DB12C2508803BBC00160861 /* License Info */ = { + isa = PBXGroup; + children = ( + 3DB12D050880427F00160861 /* InfoController.h */, + 3DB12D060880427F00160861 /* InfoController.m */, + ); + name = "License Info"; + sourceTree = ""; + }; + 3DB12C2608803BD300160861 /* License Creation */ = { + isa = PBXGroup; + children = ( + 3D9364010856724F006F1864 /* LicenseController.h */, + 3D9364020856724F006F1864 /* LicenseController.m */, + ); + name = "License Creation"; + sourceTree = ""; + }; + 3DB12C2708803BDC00160861 /* Product Controller */ = { + isa = PBXGroup; + children = ( + 3D12D1BE08520D0E0035BA78 /* ProductController.h */, + 3D12D1BF08520D0E0035BA78 /* ProductController.m */, + ); + name = "Product Controller"; + sourceTree = ""; + }; + 3DB12C2808803BE900160861 /* Key Controller */ = { + isa = PBXGroup; + children = ( + E804BCA50833AAFD00512005 /* KeyController.h */, + E804BCA60833AAFD00512005 /* KeyController.m */, + ); + name = "Key Controller"; + sourceTree = ""; + }; + 3DB12C2908803BF100160861 /* Status */ = { + isa = PBXGroup; + children = ( + 3D12D157085208CC0035BA78 /* StatusController.h */, + 3D12D158085208CC0035BA78 /* StatusController.m */, + ); + name = Status; + sourceTree = ""; + }; + 3DB12D280880431600160861 /* Nibs */ = { + isa = PBXGroup; + children = ( + 3DD108F30881725A00C4C6B4 /* About.nib */, + 29B97318FDCFA39411CA2CEA /* MainMenu.nib */, + 3DB12CFB0880423A00160861 /* LicenseInfo.nib */, + ); + name = Nibs; + sourceTree = ""; + }; + 3DB12D8E088046A600160861 /* Main Controller */ = { + isa = PBXGroup; + children = ( + 3DB12C9E08803F0300160861 /* MainController.h */, + 3DB12C9F08803F0300160861 /* MainController.m */, + ); + name = "Main Controller"; + sourceTree = ""; + }; + 3DD108B8088170A100C4C6B4 /* AQAppKit */ = { + isa = PBXGroup; + children = ( + 3DD108C5088170A600C4C6B4 /* AQAboutWindow */, + 3DD108C6088170AE00C4C6B4 /* AQTableView */, + ); + path = AQAppKit; + sourceTree = ""; + }; + 3DD108C5088170A600C4C6B4 /* AQAboutWindow */ = { + isa = PBXGroup; + children = ( + 3DD108B9088170A100C4C6B4 /* AQAboutView.h */, + 3DD108BA088170A100C4C6B4 /* AQAboutView.m */, + 3DD108BB088170A100C4C6B4 /* AQAboutWindow.h */, + 3DD108BC088170A100C4C6B4 /* AQAboutWindow.m */, + ); + name = AQAboutWindow; + sourceTree = ""; + }; + 3DD108C6088170AE00C4C6B4 /* AQTableView */ = { + isa = PBXGroup; + children = ( + 3D1606A3089D41C100526ED8 /* AQTextFieldCell.h */, + 3D1606A2089D41C100526ED8 /* AQTextFieldCell.m */, + 3D1605FF089D3DBC00526ED8 /* OAGradientTableView.h */, + 3D1605FE089D3DBC00526ED8 /* OAGradientTableView.m */, + 3DD108BD088170A100C4C6B4 /* AQTableView.h */, + 3DD108BE088170A100C4C6B4 /* AQTableView.m */, + ); + name = AQTableView; + sourceTree = ""; + }; + 3DD108FA0881727400C4C6B4 /* About */ = { + isa = PBXGroup; + children = ( + 3DD108F50881726E00C4C6B4 /* AboutController.h */, + 3DD108F60881726E00C4C6B4 /* AboutController.m */, + ); + name = About; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8D1107260486CEB800E47090 /* AquaticPrimeDeveloper */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3D9363D208565C58006F1864 /* Build configuration list for PBXNativeTarget "AquaticPrimeDeveloper" */; + buildPhases = ( + 8D1107290486CEB800E47090 /* Resources */, + 8D11072C0486CEB800E47090 /* Sources */, + 8D11072E0486CEB800E47090 /* Frameworks */, + A0BFCA2509A3F240005C04A5 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AquaticPrimeDeveloper; + productInstallPath = "$(HOME)/Applications"; + productName = AquaticPrimeDeveloper; + productReference = 8D1107320486CEB800E47090 /* AquaticPrime Developer.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 3D9363D608565C58006F1864 /* Build configuration list for PBXProject "AquaticPrime Developer" */; + hasScannedForEncodings = 1; + mainGroup = 29B97314FDCFA39411CA2CEA /* AquaticPrimeDeveloper */; + projectDirPath = ""; + targets = ( + 8D1107260486CEB800E47090 /* AquaticPrimeDeveloper */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8D1107290486CEB800E47090 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */, + 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, + 3D12D14B085206240035BA78 /* add.tiff in Resources */, + 3DF60B9E085CE0BD004C50F2 /* remove.tiff in Resources */, + 3DB12CFC0880423A00160861 /* LicenseInfo.nib in Resources */, + 3DD108F40881725A00C4C6B4 /* About.nib in Resources */, + 3DC1B057088183B10007C7E0 /* aboutbox.png in Resources */, + 3D4458EB08971CEC002901EB /* aquatic.icns in Resources */, + 3DA8074C08A694CF006C1C9F /* badge.icns in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8D11072C0486CEB800E47090 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D11072D0486CEB800E47090 /* main.m in Sources */, + E804BCA70833AAFD00512005 /* KeyController.m in Sources */, + 3D12D159085208CC0035BA78 /* StatusController.m in Sources */, + 3D12D1C008520D0E0035BA78 /* ProductController.m in Sources */, + 3D9364030856724F006F1864 /* LicenseController.m in Sources */, + 3DB12CA108803F0300160861 /* MainController.m in Sources */, + 3DB12D080880427F00160861 /* InfoController.m in Sources */, + 3DD108C0088170A100C4C6B4 /* AQAboutView.m in Sources */, + 3DD108C2088170A100C4C6B4 /* AQAboutWindow.m in Sources */, + 3DD108C4088170A100C4C6B4 /* AQTableView.m in Sources */, + 3DD108F80881726E00C4C6B4 /* AboutController.m in Sources */, + 3D160600089D3DBC00526ED8 /* OAGradientTableView.m in Sources */, + 3D1606A4089D41C100526ED8 /* AQTextFieldCell.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 089C165DFE840E0CC02AAC07 /* English */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = { + isa = PBXVariantGroup; + children = ( + 29B97319FDCFA39411CA2CEA /* English */, + ); + name = MainMenu.nib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 3D9363D308565C58006F1864 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(FRAMEWORK_SEARCH_PATHS)", + "$(SRCROOT)", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AquaticPrimeDeveloper_Prefix.pch; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + MACOSX_DEPLOYMENT_TARGET_i386 = 10.4; + MACOSX_DEPLOYMENT_TARGET_ppc = 10.3; + OTHER_LDFLAGS = ( + "-lcrypto", + libAquaticPrime.a, + "-framework", + Cocoa, + ); + PREBINDING = NO; + PRODUCT_NAME = "AquaticPrime Developer"; + WRAPPER_EXTENSION = app; + ZERO_LINK = NO; + }; + name = Development; + }; + 3D9363D408565C58006F1864 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(FRAMEWORK_SEARCH_PATHS)", + "$(SRCROOT)", + ); + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AquaticPrimeDeveloper_Prefix.pch; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_PARAMETER = YES; + GCC_WARN_UNUSED_VALUE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GENERATE_PKGINFO_FILE = NO; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + MACOSX_DEPLOYMENT_TARGET_i386 = 10.4; + MACOSX_DEPLOYMENT_TARGET_ppc = 10.3; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ( + "-lcrypto", + libAquaticPrime.a, + ); + PREBINDING = YES; + PRODUCT_NAME = "AquaticPrime Developer"; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + WARNING_CFLAGS = "-Wno-unused"; + WRAPPER_EXTENSION = app; + ZERO_LINK = NO; + }; + name = Deployment; + }; + 3D9363D508565C58006F1864 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + FRAMEWORK_SEARCH_PATHS = ( + "$(FRAMEWORK_SEARCH_PATHS)", + "$(SRCROOT)", + ); + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AquaticPrimeDeveloper_Prefix.pch; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + MACOSX_DEPLOYMENT_TARGET_i386 = 10.4; + MACOSX_DEPLOYMENT_TARGET_ppc = 10.3; + OTHER_LDFLAGS = ( + "-lcrypto", + libAquaticPrime.a, + "-framework", + Cocoa, + ); + PRODUCT_NAME = "AquaticPrime Developer"; + WRAPPER_EXTENSION = app; + ZERO_LINK = NO; + }; + name = Default; + }; + 3D9363D708565C58006F1864 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Development; + }; + 3D9363D808565C58006F1864 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Deployment; + }; + 3D9363D908565C58006F1864 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Default; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3D9363D208565C58006F1864 /* Build configuration list for PBXNativeTarget "AquaticPrimeDeveloper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3D9363D308565C58006F1864 /* Development */, + 3D9363D408565C58006F1864 /* Deployment */, + 3D9363D508565C58006F1864 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + 3D9363D608565C58006F1864 /* Build configuration list for PBXProject "AquaticPrime Developer" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3D9363D708565C58006F1864 /* Development */, + 3D9363D808565C58006F1864 /* Deployment */, + 3D9363D908565C58006F1864 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/Developer/AquaticPrimeDeveloper_Prefix.pch b/Developer/AquaticPrimeDeveloper_Prefix.pch new file mode 100644 index 0000000..f03cfaf --- /dev/null +++ b/Developer/AquaticPrimeDeveloper_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'AquaticPrime' target in the 'AquaticPrime' project. +// + +#ifdef __OBJC__ +#import +#endif diff --git a/Developer/Classes/AQAppKit/AQAboutView.h b/Developer/Classes/AQAppKit/AQAboutView.h new file mode 100755 index 0000000..70efb52 --- /dev/null +++ b/Developer/Classes/AQAppKit/AQAboutView.h @@ -0,0 +1,7 @@ +#import + +@interface AQAboutView : NSView +{ + NSImage *aboutImage; +} +@end diff --git a/Developer/Classes/AQAppKit/AQAboutView.m b/Developer/Classes/AQAppKit/AQAboutView.m new file mode 100755 index 0000000..f1cb07d --- /dev/null +++ b/Developer/Classes/AQAppKit/AQAboutView.m @@ -0,0 +1,21 @@ +#import "AQAboutView.h" + +@implementation AQAboutView + +-(void)awakeFromNib +{ + aboutImage = [NSImage imageNamed:@"aboutbox"]; + [self setNeedsDisplay:YES]; +} + +-(void)drawRect:(NSRect)rect +{ + [[NSColor clearColor] set]; + NSRectFill([self frame]); + + [aboutImage compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver]; + + [[self window] invalidateShadow]; +} + +@end diff --git a/Developer/Classes/AQAppKit/AQAboutWindow.h b/Developer/Classes/AQAppKit/AQAboutWindow.h new file mode 100755 index 0000000..ceaf766 --- /dev/null +++ b/Developer/Classes/AQAppKit/AQAboutWindow.h @@ -0,0 +1,6 @@ +#import + +@interface AQAboutWindow : NSWindow +{ +} +@end diff --git a/Developer/Classes/AQAppKit/AQAboutWindow.m b/Developer/Classes/AQAppKit/AQAboutWindow.m new file mode 100755 index 0000000..ebe3d50 --- /dev/null +++ b/Developer/Classes/AQAppKit/AQAboutWindow.m @@ -0,0 +1,46 @@ +#import "AQAboutWindow.h" + +@implementation AQAboutWindow + +- (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag +{ + NSWindow *result = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; + [result setBackgroundColor: [NSColor clearColor]]; + [result setLevel: NSStatusWindowLevel]; + [result setOpaque:NO]; + [result setHasShadow:YES]; + [result setDelegate:self]; + return result; +} + +- (BOOL)canBecomeKeyWindow +{ + return YES; +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (BOOL)becomeFirstResponder +{ + return YES; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + [self orderOut:self]; +} + +- (void)keyDown:(NSEvent *)theEvent +{ + [self orderOut:self]; +} + +- (void)windowDidResignKey:(NSNotification *)aNotification +{ + [self orderOut:self]; +} + +@end diff --git a/Developer/Classes/AQAppKit/AQTableView.h b/Developer/Classes/AQAppKit/AQTableView.h new file mode 100644 index 0000000..2df2c7b --- /dev/null +++ b/Developer/Classes/AQAppKit/AQTableView.h @@ -0,0 +1,36 @@ +// +// AQTableView.h +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import + +@interface AQTableView : NSTableView +{ +} + +// Delegate method +- (void)deleteItemAtIndex:(int)index; + +@end diff --git a/Developer/Classes/AQAppKit/AQTableView.m b/Developer/Classes/AQAppKit/AQTableView.m new file mode 100644 index 0000000..81cc937 --- /dev/null +++ b/Developer/Classes/AQAppKit/AQTableView.m @@ -0,0 +1,49 @@ +// +// AQTableView.m +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "AQTableView.h" + +@implementation AQTableView + +- (void)keyDown:(NSEvent *)event +{ + unichar key = [[event charactersIgnoringModifiers] characterAtIndex:0]; + + if ( (key == NSDeleteCharacter || key == NSDeleteFunctionKey) && + ([self numberOfRows] > 0) && ([self selectedRow] != -1) ) + { + [self deleteItemAtIndex:[self selectedRow]]; + } else { + [super keyDown:event]; + } +} + +- (void)deleteItemAtIndex:(int)row +{ + [[self delegate] deleteItemAtIndex:row]; +} + +@end diff --git a/Developer/Classes/AQAppKit/AQTextFieldCell.h b/Developer/Classes/AQAppKit/AQTextFieldCell.h new file mode 100644 index 0000000..dfa51ec --- /dev/null +++ b/Developer/Classes/AQAppKit/AQTextFieldCell.h @@ -0,0 +1,8 @@ +/* AQTextFieldCell */ + +#import + +@interface AQTextFieldCell : NSTextFieldCell +{ +} +@end diff --git a/Developer/Classes/AQAppKit/AQTextFieldCell.m b/Developer/Classes/AQAppKit/AQTextFieldCell.m new file mode 100644 index 0000000..d9a07b1 --- /dev/null +++ b/Developer/Classes/AQAppKit/AQTextFieldCell.m @@ -0,0 +1,26 @@ +#import "AQTextFieldCell.h" + +@implementation AQTextFieldCell + +// Deal with drawing the text inside the cell ourselves +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView +{ + // First get the string and string attributes + NSString *string = [self stringValue]; + NSMutableDictionary *attribs = [[[self attributedStringValue] attributesAtIndex:0 effectiveRange:nil] mutableCopy]; + + // If the cell is selected and its control view is the first responder, draw the text white + if ([self isHighlighted]) + [attribs setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName]; + + // Then adjust the drawing rectangle and draw the text so that it is centered vertically + NSSize stringSize = [string sizeWithAttributes:attribs]; + cellFrame.origin.x += 4.0; + cellFrame.size.width -= 4.0; + cellFrame.origin.y += (cellFrame.size.height - stringSize.height) / 2; + cellFrame.size.height = stringSize.height; + [string drawInRect:cellFrame withAttributes:attribs]; + [attribs release]; +} + +@end diff --git a/Developer/Classes/AQAppKit/OAGradientTableView.h b/Developer/Classes/AQAppKit/OAGradientTableView.h new file mode 100644 index 0000000..466c197 --- /dev/null +++ b/Developer/Classes/AQAppKit/OAGradientTableView.h @@ -0,0 +1,23 @@ +// Copyright 2003-2004 Omni Development, Inc. All rights reserved. +// +// This software may only be used and reproduced according to the +// terms in the file OmniSourceLicense.html, which should be +// distributed with this project and can also be found at +// . +// + +#import +#import "AQTableView.h" + +// For this to look right your cell class must return -[NSColor textBackgroundColor] from -textColor when it is highlighted. See OATextWithIconCell for example. + +@interface OAGradientTableView : AQTableView +{ + struct { + unsigned int acceptsFirstMouse:1; + } flags; +} + +- (void)setAcceptsFirstMouse:(BOOL)flag; + +@end diff --git a/Developer/Classes/AQAppKit/OAGradientTableView.m b/Developer/Classes/AQAppKit/OAGradientTableView.m new file mode 100644 index 0000000..44009e5 --- /dev/null +++ b/Developer/Classes/AQAppKit/OAGradientTableView.m @@ -0,0 +1,161 @@ +// Copyright 2003-2004 Omni Development, Inc. All rights reserved. +// +// This software may only be used and reproduced according to the +// terms in the file OmniSourceLicense.html, which should be +// distributed with this project and can also be found at +// . + +#import "OAGradientTableView.h" +#import "AQTextFieldCell.h" + +@interface OAGradientTableView (Private) +@end + +/* + CoreGraphics gradient helpers +*/ + +typedef struct { + float red1, green1, blue1, alpha1; + float red2, green2, blue2, alpha2; +} _twoColorsType; + +static void _linearColorBlendFunction(void *info, const float *in, float *out) +{ + _twoColorsType *twoColors = info; + + out[0] = (1.0 - *in) * twoColors->red1 + *in * twoColors->red2; + out[1] = (1.0 - *in) * twoColors->green1 + *in * twoColors->green2; + out[2] = (1.0 - *in) * twoColors->blue1 + *in * twoColors->blue2; + out[3] = (1.0 - *in) * twoColors->alpha1 + *in * twoColors->alpha2; +} + +static void _linearColorReleaseInfoFunction(void *info) +{ + free(info); +} + +static const CGFunctionCallbacks linearFunctionCallbacks = {0, &_linearColorBlendFunction, &_linearColorReleaseInfoFunction}; + +/* + End CoreGraphics gradient helpers +*/ + +@implementation OAGradientTableView + +- (void)awakeFromNib +{ + [[self tableColumnWithIdentifier:@"productColumn"] setDataCell:[[AQTextFieldCell alloc] init]]; +} + +// API + +- (void)setAcceptsFirstMouse:(BOOL)flag; +{ + flags.acceptsFirstMouse = flag; +} + +// NSView subclass + +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent +{ + if (flags.acceptsFirstMouse) + return [NSApp isActive]; + + return [super acceptsFirstMouse:theEvent]; +} + +// NSTableView subclass + +- (id)_highlightColorForCell:(NSCell *)cell; +{ + return nil; +} + +- (void)highlightSelectionInClipRect:(NSRect)rect; +{ + // Take the color apart + NSColor *alternateSelectedControlColor = [NSColor alternateSelectedControlColor]; + float hue, saturation, brightness, alpha; + [[alternateSelectedControlColor colorUsingColorSpaceName:NSDeviceRGBColorSpace] getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha]; + + // Create synthetic darker and lighter versions + // NSColor *lighterColor = [NSColor colorWithDeviceHue:hue - (1.0/120.0) saturation:MAX(0.0, saturation-0.12) brightness:MIN(1.0, brightness+0.045) alpha:alpha]; + NSColor *lighterColor = [NSColor colorWithDeviceHue:hue saturation:MAX(0.0, saturation-.12) brightness:MIN(1.0, brightness+0.30) alpha:alpha]; + NSColor *darkerColor = [NSColor colorWithDeviceHue:hue saturation:MIN(1.0, (saturation > .04) ? saturation+0.12 : 0.0) brightness:MAX(0.0, brightness-0.045) alpha:alpha]; + + // If this view isn't key, use the gray version of the dark color. Note that this varies from the standard gray version that NSCell returns as its highlightColorWithFrame: when the cell is not in a key view, in that this is a lot darker. Mike and I think this is justified for this kind of view -- if you're using the dark selection color to show the selected status, it makes sense to leave it dark. + if ([[self window] firstResponder] != self || ![[self window] isKeyWindow]) { + alternateSelectedControlColor = [[alternateSelectedControlColor colorUsingColorSpaceName:NSDeviceWhiteColorSpace] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + lighterColor = [[lighterColor colorUsingColorSpaceName:NSDeviceWhiteColorSpace] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + darkerColor = [[darkerColor colorUsingColorSpaceName:NSDeviceWhiteColorSpace] colorUsingColorSpaceName:NSDeviceRGBColorSpace]; + } + + // Set up the helper function for drawing washes + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + _twoColorsType *twoColors = malloc(sizeof(_twoColorsType)); // We malloc() the helper data because we may draw this wash during printing, in which case it won't necessarily be evaluated immediately. We need for all the data the shading function needs to draw to potentially outlive us. + [lighterColor getRed:&twoColors->red1 green:&twoColors->green1 blue:&twoColors->blue1 alpha:&twoColors->alpha1]; + [darkerColor getRed:&twoColors->red2 green:&twoColors->green2 blue:&twoColors->blue2 alpha:&twoColors->alpha2]; + static const float domainAndRange[8] = {0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0}; + CGFunctionRef linearBlendFunctionRef = CGFunctionCreate(twoColors, 1, domainAndRange, 4, domainAndRange, &linearFunctionCallbacks); + +#ifdef DO_IT_PANTHER + NSIndexSet *selectedRowIndexes = [self selectedRowIndexes]; + unsigned int rowIndex = [selectedRowIndexes indexGreaterThanOrEqualToIndex:0]; +#else + NSEnumerator *selectedRowEnumerator = [self selectedRowEnumerator]; + NSNumber *rowIndexNumber = [selectedRowEnumerator nextObject]; + unsigned int rowIndex = rowIndexNumber ? [rowIndexNumber unsignedIntValue] : NSNotFound; +#endif + + while (rowIndex != NSNotFound) { + unsigned int endOfCurrentRunRowIndex, newRowIndex = rowIndex; + do { + endOfCurrentRunRowIndex = newRowIndex; +#ifdef DO_IT_PANTHER + newRowIndex = [selectedRowIndexes indexGreaterThanIndex:endOfCurrentRunRowIndex]; +#else + rowIndexNumber = [selectedRowEnumerator nextObject]; + newRowIndex = rowIndexNumber ? [rowIndexNumber unsignedIntValue] : NSNotFound; +#endif + } while (newRowIndex == endOfCurrentRunRowIndex + 1); + + NSRect rowRect = NSUnionRect([self rectOfRow:rowIndex], [self rectOfRow:endOfCurrentRunRowIndex]); + + NSRect topBar, washRect; + NSDivideRect(rowRect, &topBar, &washRect, 1.0, NSMinYEdge); + + // Draw the top line of pixels of the selected row in the alternateSelectedControlColor + [alternateSelectedControlColor set]; + NSRectFill(topBar); + + // Draw a soft wash underneath it + CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; + CGContextSaveGState(context); { + CGContextClipToRect(context, (CGRect){{NSMinX(washRect), NSMinY(washRect)}, {NSWidth(washRect), NSHeight(washRect)}}); + CGShadingRef cgShading = CGShadingCreateAxial(colorSpace, CGPointMake(0, NSMinY(washRect)), CGPointMake(0, NSMaxY(washRect)), linearBlendFunctionRef, NO, NO); + CGContextDrawShading(context, cgShading); + CGShadingRelease(cgShading); + } CGContextRestoreGState(context); + + rowIndex = newRowIndex; + } + + + CGFunctionRelease(linearBlendFunctionRef); + CGColorSpaceRelease(colorSpace); +} + +- (void)selectRow:(int)row byExtendingSelection:(BOOL)extend; +{ + [super selectRow:row byExtendingSelection:extend]; + [self setNeedsDisplay:YES]; // we display extra because we draw multiple contiguous selected rows differently, so changing one row's selection can change how others draw. +} + +- (void)deselectRow:(int)row; +{ + [super deselectRow:row]; + [self setNeedsDisplay:YES]; // we display extra because we draw multiple contiguous selected rows differently, so changing one row's selection can change how others draw. +} + +@end diff --git a/Developer/Classes/AboutController.h b/Developer/Classes/AboutController.h new file mode 100644 index 0000000..f1b24e2 --- /dev/null +++ b/Developer/Classes/AboutController.h @@ -0,0 +1,9 @@ +/* AboutController */ + +#import + +@interface AboutController : NSWindowController +{ + IBOutlet NSWindow *window; +} +@end diff --git a/Developer/Classes/AboutController.m b/Developer/Classes/AboutController.m new file mode 100644 index 0000000..fb8b290 --- /dev/null +++ b/Developer/Classes/AboutController.m @@ -0,0 +1,11 @@ +#import "AboutController.h" + +@implementation AboutController + +- (id)init +{ + self = [super initWithWindowNibName:@"About"]; + return self; +} + +@end diff --git a/Developer/Classes/AquaticPrime.h b/Developer/Classes/AquaticPrime.h new file mode 100644 index 0000000..40c4b31 --- /dev/null +++ b/Developer/Classes/AquaticPrime.h @@ -0,0 +1,71 @@ +// +// AquaticPrime.h +// AquaticPrime Framework +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import +#include +#include +#include + +@interface AquaticPrime : NSObject { + RSA *rsaKey; + + NSString *aqError; + NSString *hash; + NSArray *blacklist; +} + +// Creation ++ (id)aquaticPrimeWithKey:(NSString *)key; ++ (id)aquaticPrimeWithKey:(NSString *)key privateKey:(NSString *)privateKey; +- (id)initWithKey:(NSString *)key; +- (id)initWithKey:(NSString *)key privateKey:(NSString *)privateKey; + +// Getters & Setters +- (BOOL)setKey:(NSString *)key; +- (BOOL)setKey:(NSString *)key privateKey:(NSString *)privateKey; +- (NSString *)key; +- (NSString *)privateKey; +- (void)setHash:(NSString *)newHash; +- (NSString *)hash; + +// Generating license data/files +- (NSData*)licenseDataForDictionary:(NSDictionary *)dict; +- (BOOL)writeLicenseFileForDictionary:(NSDictionary *)dict toPath:(NSString *)path; + +// Validating license data/files +- (NSDictionary *)dictionaryForLicenseData:(NSData *)data; +- (NSDictionary *)dictionaryForLicenseFile:(NSString *)path; +- (BOOL)verifyLicenseData:(NSData *)data; +- (BOOL)verifyLicenseFile:(NSString *)path; + +// Blacklisting +- (void)setBlacklist:(NSArray *)hashArray; + +// Error handling +- (void)_setError:(NSString *)err; +- (NSString *)getLastError; + +@end diff --git a/Developer/Classes/AquaticPrime.m b/Developer/Classes/AquaticPrime.m new file mode 100644 index 0000000..fb63bc3 --- /dev/null +++ b/Developer/Classes/AquaticPrime.m @@ -0,0 +1,359 @@ +// +// AquaticPrime.m +// AquaticPrime Framework +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "AquaticPrime.h" + +@implementation AquaticPrime + +- (id)init +{ + return [self initWithKey:nil privateKey:nil]; +} + +- (id)initWithKey:(NSString *)key +{ + return [self initWithKey:key privateKey:nil]; +} + +- (id)initWithKey:(NSString *)key privateKey:(NSString *)privateKey +{ + ERR_load_crypto_strings(); + + if (![super init]) + return nil; + + aqError = [[NSString alloc] init]; + blacklist = [[NSArray alloc] init]; + hash = [[NSString alloc] init]; + + [self setKey:key privateKey:privateKey]; + + return self; +} + +- (void)dealloc +{ + ERR_free_strings(); + + if (rsaKey) + RSA_free(rsaKey); + + if (blacklist) + [blacklist release]; + if (aqError) + [aqError release]; + if (hash) + [hash release]; + + [super dealloc]; +} + ++ (id)aquaticPrimeWithKey:(NSString *)key privateKey:(NSString *)privateKey +{ + return [[[AquaticPrime alloc] initWithKey:key privateKey:privateKey] autorelease]; +} + ++ (id)aquaticPrimeWithKey:(NSString *)key +{ + return [[[AquaticPrime alloc] initWithKey:key privateKey:nil] autorelease]; +} + +- (BOOL)setKey:(NSString *)key +{ + return [self setKey:key privateKey:nil]; +} + +- (BOOL)setKey:(NSString *)key privateKey:(NSString *)privateKey +{ + // Must have public modulus, private key is optional + if (!key || [key isEqualToString:@""]) { + [self _setError:@"Empty public key parameter"]; + return NO; + } + + if (rsaKey) + RSA_free(rsaKey); + + rsaKey = RSA_new(); + + // We are using the constant public exponent e = 3 + BN_dec2bn(&rsaKey->e, "3"); + + // Determine if we have hex or decimal values + int result; + if ([[key lowercaseString] hasPrefix:@"0x"]) + result = BN_hex2bn(&rsaKey->n, (const char *)[[key substringFromIndex:2] UTF8String]); + else + result = BN_dec2bn(&rsaKey->n, (const char *)[key UTF8String]); + + if (!result) { + [self _setError:[NSString stringWithUTF8String:(char*)ERR_error_string(ERR_get_error(), NULL)]]; + return NO; + } + + // Do the private portion if it exists + if (privateKey && ![privateKey isEqualToString:@""]) { + if ([[privateKey lowercaseString] hasPrefix:@"0x"]) + result = BN_hex2bn(&rsaKey->d, (const char *)[[privateKey substringFromIndex:2] UTF8String]); + else + result = BN_dec2bn(&rsaKey->d, (const char *)[privateKey UTF8String]); + + if (!result) { + [self _setError:[NSString stringWithUTF8String:(char*)ERR_error_string(ERR_get_error(), NULL)]]; + return NO; + } + } + + return YES; +} + +- (NSString *)key +{ + if (!rsaKey || !rsaKey->n) + return nil; + + char *cString = BN_bn2hex(rsaKey->n); + + NSString *nString = [[NSString alloc] initWithUTF8String:cString]; + OPENSSL_free(cString); + + return nString; +} + +- (NSString *)privateKey +{ + if (!rsaKey || !rsaKey->d) + return nil; + + char *cString = BN_bn2hex(rsaKey->d); + + NSString *dString = [[NSString alloc] initWithUTF8String:cString]; + OPENSSL_free(cString); + + return dString; +} + +- (void)setHash:(NSString *)newHash +{ + if (hash) + [hash release]; + + hash = [newHash retain]; +} + +- (NSString *)hash +{ + return hash; +} + +#pragma mark Blacklisting + +// This array should contain a list of NSStrings representing hexadecimal hashcodes for blacklisted licenses +- (void)setBlacklist:(NSArray*)hashArray +{ + if (blacklist) + [blacklist release]; + + blacklist = [hashArray retain]; +} + +#pragma mark Signing + +- (NSData*)licenseDataForDictionary:(NSDictionary*)dict +{ + // Make sure we have a good key + if (!rsaKey || !rsaKey->n || !rsaKey->d) { + [self _setError:@"RSA key is invalid"]; + return nil; + } + + // Grab all values from the dictionary + NSMutableArray *keyArray = [NSMutableArray arrayWithArray:[dict allKeys]]; + NSMutableData *dictData = [NSMutableData data]; + + // Sort the keys so we always have a uniform order + [keyArray sortUsingSelector:@selector(caseInsensitiveCompare:)]; + + int i; + for (i = 0; i < [keyArray count]; i++) + { + id curValue = [dict objectForKey:[keyArray objectAtIndex:i]]; + char *desc = (char *)[[curValue description] UTF8String]; + // We use strlen instead of [string length] so we can get all the bytes of accented characters + [dictData appendBytes:desc length:strlen(desc)]; + } + + // Hash the data + unsigned char digest[20]; + SHA1([dictData bytes], [dictData length], digest); + + // Create the signature from 20 byte hash + int rsaLength = RSA_size(rsaKey); + unsigned char *signature = (unsigned char*)malloc(rsaLength); + int bytes = RSA_private_encrypt(20, digest, signature, rsaKey, RSA_PKCS1_PADDING); + + if (bytes == -1) { + [self _setError:[NSString stringWithUTF8String:(char*)ERR_error_string(ERR_get_error(), NULL)]]; + return nil; + } + + // Create the license dictionary + NSMutableDictionary *licenseDict = [NSMutableDictionary dictionaryWithDictionary:dict]; + [licenseDict setObject:[NSData dataWithBytes:signature length:bytes] forKey:@"Signature"]; + + // Create the data from the dictionary + NSString *error; + NSData *licenseFile = [[NSPropertyListSerialization dataFromPropertyList:licenseDict + format:kCFPropertyListXMLFormat_v1_0 + errorDescription:&error] retain]; + + if (!licenseFile) { + [self _setError:error]; + return nil; + } + + return licenseFile; +} + +- (BOOL)writeLicenseFileForDictionary:(NSDictionary*)dict toPath:(NSString *)path +{ + NSData *licenseFile = [self licenseDataForDictionary:dict]; + + if (!licenseFile) + return NO; + + return [licenseFile writeToFile:path atomically:YES]; +} + +// This method only logs errors on developer problems, so don't expect to grab an error message if it's just an invalid license +- (NSDictionary*)dictionaryForLicenseData:(NSData *)data +{ + // Make sure public key is set up + if (!rsaKey || !rsaKey->n) { + [self _setError:@"RSA key is invalid"]; + return nil; + } + + // Create a dictionary from the data + NSPropertyListFormat format; + NSString *error; + NSMutableDictionary *licenseDict = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&error]; + if (error) + return nil; + + NSData *signature = [licenseDict objectForKey:@"Signature"]; + if (!signature) + return nil; + + // Decrypt the signature - should get 20 bytes back + unsigned char checkDigest[20]; + if (RSA_public_decrypt([signature length], [signature bytes], checkDigest, rsaKey, RSA_PKCS1_PADDING) != 20) + return nil; + + // Make sure the license hash isn't on the blacklist + NSMutableString *hashCheck = [NSMutableString string]; + int hashIndex; + for (hashIndex = 0; hashIndex < 20; hashIndex++) + [hashCheck appendFormat:@"%02x", checkDigest[hashIndex]]; + + // Store the license hash in case we need it later + [self setHash:hashCheck]; + + if (blacklist && [blacklist containsObject:hashCheck]) + return nil; + + // Remove the signature element + [licenseDict removeObjectForKey:@"Signature"]; + + // Grab all values from the dictionary + NSMutableArray *keyArray = [NSMutableArray arrayWithArray:[licenseDict allKeys]]; + NSMutableData *dictData = [NSMutableData data]; + + // Sort the keys so we always have a uniform order + [keyArray sortUsingSelector:@selector(caseInsensitiveCompare:)]; + + int objectIndex; + for (objectIndex = 0; objectIndex < [keyArray count]; objectIndex++) + { + id currentValue = [licenseDict objectForKey:[keyArray objectAtIndex:objectIndex]]; + char *description = (char *)[[currentValue description] UTF8String]; + // We use strlen instead of [string length] so we can get all the bytes of accented characters + [dictData appendBytes:description length:strlen(description)]; + } + + // Hash the data + unsigned char digest[20]; + SHA1([dictData bytes], [dictData length], digest); + + // Check if the signature is a match + int checkIndex; + for (checkIndex = 0; checkIndex < 20; checkIndex++) { + if (checkDigest[checkIndex] ^ digest[checkIndex]) + return nil; + } + + return [NSDictionary dictionaryWithDictionary:licenseDict]; +} + +- (NSDictionary*)dictionaryForLicenseFile:(NSString *)path +{ + NSData *licenseFile = [NSData dataWithContentsOfFile:path]; + + if (!licenseFile) + return nil; + + return [self dictionaryForLicenseData:licenseFile]; +} + +- (BOOL)verifyLicenseData:(NSData *)data +{ + if ([self dictionaryForLicenseData:data]) + return YES; + else + return NO; +} + +- (BOOL)verifyLicenseFile:(NSString *)path +{ + NSData *data = [NSData dataWithContentsOfFile:path]; + return [self verifyLicenseData:data]; +} + +#pragma mark Error Handling + +- (void)_setError:(NSString *)err +{ + if (aqError) + [aqError release]; + aqError = [err retain]; +} + +- (NSString*)getLastError +{ + return aqError; +} + +@end diff --git a/Developer/Classes/InfoController.h b/Developer/Classes/InfoController.h new file mode 100644 index 0000000..916db26 --- /dev/null +++ b/Developer/Classes/InfoController.h @@ -0,0 +1,22 @@ +/* InfoController */ + +#import + +@interface InfoController : NSWindowController +{ + IBOutlet NSView *licenseInfoView; + IBOutlet NSWindow *licenseWindow; + IBOutlet NSTableView *keyValueInfoTable; + IBOutlet NSTextField *licenseValidField; + IBOutlet NSTextField *hashField; + + NSMutableArray *keyInfoArray; + NSMutableArray *valueInfoArray; + BOOL isLicenseValid; + NSString *licenseValidForProduct; + NSString *hash; +} + +- (BOOL)readLicenseAtPath:(NSString*)licensePath; + +@end \ No newline at end of file diff --git a/Developer/Classes/InfoController.m b/Developer/Classes/InfoController.m new file mode 100644 index 0000000..eb1e10c --- /dev/null +++ b/Developer/Classes/InfoController.m @@ -0,0 +1,152 @@ +// +// InfoController.m +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "InfoController.h" +#import "KeyController.h" +#import "AquaticPrime.h" + +@implementation InfoController + +- (id)init +{ + self = [super initWithWindowNibName:@"LicenseInfo"]; + return self; +} + +- (void)awakeFromNib +{ + keyInfoArray = [[NSMutableArray alloc] init]; + valueInfoArray = [[NSMutableArray alloc] init]; + + [[self window] registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]]; +} + +- (IBAction)closeLicenseInfoWindow:(id)sender +{ + [[self window] orderOut:self]; +} + +- (void)concludeDragOperation:(id )sender +{ +} + +- (void)draggingEnded:(id )sender +{ +} + +- (void)draggingEntered:(id )sender +{ +} + +- (void)draggingExited:(id )sender +{ +} + +- (BOOL)prepareForDragOperation:(id )sender +{ + return YES; +} + +- (BOOL)performDragOperation:(id )sender +{ + NSArray *filenames = [[sender draggingPasteboard] propertyListForType:NSFilenamesPboardType]; + + if ([filenames count] != 1) + return NO; + + NSString *licensePath = [filenames objectAtIndex:0]; + if (![self readLicenseAtPath:licensePath]) + return NO; + + [licenseWindow setTitle:[licensePath lastPathComponent]]; + NSString *valid = isLicenseValid ? [NSString stringWithFormat:@"This license is valid for %@", licenseValidForProduct] + : @"This license is invalid"; + [licenseValidField setStringValue:valid]; + [hashField setStringValue:[NSString stringWithFormat:@"License Hash: %@", hash]]; + [[self window] setContentView:licenseInfoView]; + + return YES; +} + +- (BOOL)readLicenseAtPath:(NSString*)licensePath +{ + NSDictionary *licenseDictionary; + + // If it doesn't have a signature, don't accept the drop + if (![[NSDictionary dictionaryWithContentsOfFile:licensePath] objectForKey:@"Signature"]) + return NO; + + // Grab all the product keys that we have + NSDictionary *productKeyDictionary = [[KeyController sharedInstance] allPublicKeys]; + NSArray *productArray = [productKeyDictionary allKeys]; + + // Determine if the license is valid for any of the products + int productIndex; + for (productIndex = 0; productIndex < [productArray count]; productIndex++) + { + NSString *currentProduct = [productArray objectAtIndex:productIndex]; + NSData *publicKey = [productKeyDictionary objectForKey:currentProduct]; + NSMutableString *publicKeyString = [NSMutableString stringWithString:[publicKey description]]; + [publicKeyString replaceOccurrencesOfString:@" " withString:@"" options:nil range:NSMakeRange(0, [publicKeyString length])]; + [publicKeyString replaceOccurrencesOfString:@"<" withString:@"" options:nil range:NSMakeRange(0, [publicKeyString length])]; + [publicKeyString replaceOccurrencesOfString:@">" withString:@"" options:nil range:NSMakeRange(0, [publicKeyString length])]; + + AquaticPrime *licenseChecker = [AquaticPrime aquaticPrimeWithKey:[NSString stringWithFormat:@"0x%@", publicKeyString]]; + licenseDictionary = [licenseChecker dictionaryForLicenseFile:licensePath]; + + if (licenseDictionary) { + keyInfoArray = [[licenseDictionary allKeys] retain]; + valueInfoArray = [[licenseDictionary allValues] retain]; + hash = (NSString *)[licenseChecker hash]; + isLicenseValid = YES; + licenseValidForProduct = [currentProduct retain]; + return YES; + } + } + + // At this point, the license is invalid, but we show the key-value pairs anyway + NSMutableDictionary *badLicenseDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:licensePath]; + [badLicenseDictionary removeObjectForKey:@"Signature"]; + keyInfoArray = [[NSMutableArray arrayWithArray:[badLicenseDictionary allKeys]] retain]; + valueInfoArray = [[NSMutableArray arrayWithArray:[badLicenseDictionary allValues]] retain]; + + return YES; +} + +- (int)numberOfRowsInTableView:(NSTableView *)tableView +{ + return [keyInfoArray count]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row +{ + if ([[tableColumn identifier] isEqualToString:@"keyInfoColumn"]) + return [keyInfoArray objectAtIndex:row]; + else + return [valueInfoArray objectAtIndex:row]; +} + +@end \ No newline at end of file diff --git a/Developer/Classes/KeyController.h b/Developer/Classes/KeyController.h new file mode 100644 index 0000000..81866c7 --- /dev/null +++ b/Developer/Classes/KeyController.h @@ -0,0 +1,56 @@ +// +// KeyController.h +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import +#include + +@interface KeyController : NSObject +{ + IBOutlet NSButton *generateButton; + IBOutlet NSPopUpButton *bitSelectPopUp; + IBOutlet NSTextView *rsaKeyView; + IBOutlet NSTextView *publicKeyView; + IBOutlet NSTextView *privateKeyView; + IBOutlet NSTabView *tabView; + + RSA *rsaKey; + + IBOutlet id productController; + IBOutlet id statusController; +} + ++ (KeyController *)sharedInstance; +- (NSString*)publicKey; +- (NSString*)privateKey; +- (NSDictionary*)allPublicKeys; +- (IBAction)generateKey:(id)sender; +- (void)generateKeyForProduct:(NSString *)productName; +- (void)populateKeyView; +- (void)viewKeysForCurrentProduct; +- (BOOL)loadKeysForProduct:(NSString *)productName; +- (BOOL)saveKeysForProduct:(NSString *)productName; + +@end diff --git a/Developer/Classes/KeyController.m b/Developer/Classes/KeyController.m new file mode 100644 index 0000000..3c18d58 --- /dev/null +++ b/Developer/Classes/KeyController.m @@ -0,0 +1,365 @@ +// +// KeyController.m +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "KeyController.h" +#import "ProductController.h" +#import "StatusController.h" + +@implementation KeyController + +#pragma mark Setup + +static KeyController *sharedInstance = nil; + +- (id)init +{ + if (sharedInstance) { + [self dealloc]; + } else { + sharedInstance = [super init]; + } + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(viewKeysForCurrentProduct) name:@"ProductSelected" object:nil]; + + return sharedInstance; +} + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)aMenuItem +{ + if ([[aMenuItem title] isEqualToString:@"Export Keys..."]) { + if (![productController currentProduct]) + return NO; + } + + return YES; +} + ++ (KeyController *)sharedInstance +{ + return sharedInstance ? sharedInstance : [[self alloc] init]; +} + +#pragma mark Getting Keys + +- (NSString *)publicKey +{ + NSString *nString; + char *cString; + + if (!rsaKey->n) + return nil; + + cString = BN_bn2hex(rsaKey->n); + + nString = [[[NSString stringWithFormat:@"0x%s", cString] retain] autorelease]; + OPENSSL_free(cString); + + return nString; +} + +- (NSString *)privateKey +{ + NSString *nString; + char *cString; + + if (!rsaKey->d) + return nil; + + cString = BN_bn2hex(rsaKey->d); + + nString = [[[NSString stringWithFormat:@"0x%s", cString] retain] autorelease]; + OPENSSL_free(cString); + + return nString; +} + +- (NSDictionary*)allPublicKeys +{ + NSArray *products = [productController allProducts]; + NSMutableDictionary *productKeyDictionary = [NSMutableDictionary dictionary]; + + int productIndex; + for (productIndex = 0; productIndex < [products count]; productIndex++) + { + NSString *productPath = [[@"~/Library/Application Support/Aquatic/Product Keys" stringByExpandingTildeInPath] + stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", [products objectAtIndex:productIndex]]]; + + // Load the public key + NSData *pubData = [[NSDictionary dictionaryWithContentsOfFile:productPath] objectForKey:@"Public Key"]; + + [productKeyDictionary setObject:pubData forKey:[products objectAtIndex:productIndex]]; + } + + return productKeyDictionary; +} + +#pragma mark Viewing Keys + +- (void)viewKeysForCurrentProduct +{ + if ([productController currentProduct]) { + [generateButton setEnabled:YES]; + [self loadKeysForProduct:[productController currentProduct]]; + } + else { + [generateButton setEnabled:NO]; + [rsaKeyView setString:@""]; + [publicKeyView setString:@""]; + [privateKeyView setString:@""]; + } +} + +#define WINDOW_THRESH 30 + +- (void)populateKeyView +{ + // The public key + NSString *pubKey = [NSString stringWithFormat:@"0x%s", BN_bn2hex(rsaKey->n)]; + // How many characters we have left + int lengthLeft = [pubKey length]; + // Where we are now + int curPos = 0; + + NSMutableString *pubConstruct = [NSMutableString stringWithString:@"\n\t// This string is specially constructed to prevent key replacement \ + // *** Begin Public Key ***\n\tNSMutableString *key = [NSMutableString string];\n"]; + + while ((lengthLeft - WINDOW_THRESH) > 0) { + // Logic to check for repeats + int repeated = 0; + char charBuf = 0; + int i; + for (i = curPos; i < WINDOW_THRESH + curPos; i++) { + // We have a repeat! + if (charBuf == [pubKey characterAtIndex:i]) { + // Print up to repeat + [pubConstruct appendString:[NSString stringWithFormat:@"\t[key appendString:@\"%@\"];\n", [pubKey substringWithRange:NSMakeRange(curPos, (i-1) - curPos)]]]; + //Do the repeat + [pubConstruct appendString:[NSString stringWithFormat:@"\t[key appendString:@\"%@\"];\n", [pubKey substringWithRange:NSMakeRange(i-1, 1)]]]; + [pubConstruct appendString:[NSString stringWithFormat:@"\t[key appendString:@\"%@\"];\n", [pubKey substringWithRange:NSMakeRange(i, 1)]]]; + // Finish the line + [pubConstruct appendString:[NSString stringWithFormat:@"\t[key appendString:@\"%@\"];\n", [pubKey substringWithRange:NSMakeRange(i+1, (WINDOW_THRESH + curPos) - (i+1))]]]; + repeated = 1; + break; + } + charBuf = [pubKey characterAtIndex:i]; + } + // No repeats + if (!repeated) + [pubConstruct appendString:[NSString stringWithFormat:@"\t[key appendString:@\"%@\"];\n", [pubKey substringWithRange:NSMakeRange(curPos, WINDOW_THRESH)]]]; + + lengthLeft -= WINDOW_THRESH; + curPos += WINDOW_THRESH; + } + [pubConstruct appendString:[NSString stringWithFormat:@"\t[key appendString:@\"%@\"];\n\t// *** End Public Key *** \n", [pubKey substringWithRange:NSMakeRange(curPos, lengthLeft)]]]; + + // Populate key view + [rsaKeyView setString:pubConstruct]; + [publicKeyView setString:[self publicKey]]; + [privateKeyView setString:[self privateKey]]; +} + +#pragma mark Key Generation + +- (IBAction)generateKey:(id)sender +{ + if ([productController currentProduct]) + [self generateKeyForProduct:[productController currentProduct]]; +} + +- (void)generateKeyForProduct:(NSString *)productName +{ + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *supportDir = [@"~/Library/Application Support/Aquatic" stringByExpandingTildeInPath]; + NSString *keyDir = [supportDir stringByAppendingString:@"/Product Keys"]; + NSString *productPath = [keyDir stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", productName]]; + NSString *warningString = [NSString stringWithFormat: + @"Are you sure you want to generate a new key for %@?", productName]; + + [tabView selectLastTabViewItem:self]; + + if ([fm fileExistsAtPath:productPath]) { + if (NSRunInformationalAlertPanel(warningString, @"The old product key will be erased.", @"OK", @"Cancel", nil) == NSAlertAlternateReturn) + return; + else + [fm movePath:productPath toPath:[productPath stringByAppendingString:@".old"] handler:nil]; + } + + if (rsaKey) + RSA_free(rsaKey); + + rsaKey = RSA_generate_key(1024, 3, NULL, NULL); + + [self populateKeyView]; + [statusController setStatus:@"Generated 1024-bit key" duration:2.5]; + [self saveKeysForProduct:productName]; + + // Notify about the new key + [[NSNotificationCenter defaultCenter] postNotificationName:@"NewKeyGenerated" object:nil]; +} + +#pragma mark Loading and Saving + +// loadKeysForProduct: returns YES if the pair already exists, otherwise generates a new key and returns NO +- (BOOL)loadKeysForProduct:(NSString *)productName +{ + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *productPath = [[@"~/Library/Application Support/Aquatic/Product Keys" stringByExpandingTildeInPath] + stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", productName]]; + + // Generate a key if it doesn't exist + if (![fm fileExistsAtPath:productPath]) { + [self generateKey:self]; + return NO; + } + + // Load the dict + NSDictionary *keyDict = [NSDictionary dictionaryWithContentsOfFile:productPath]; + NSData *pubData = [keyDict objectForKey:@"Public Key"]; + NSData *privData = [keyDict objectForKey:@"Private Key"]; + + if (rsaKey) + RSA_free(rsaKey); + + rsaKey = RSA_new(); + rsaKey->n = BN_bin2bn([pubData bytes], [pubData length], NULL); + rsaKey->d = BN_bin2bn([privData bytes], [privData length], NULL); + + [self populateKeyView]; + //[statusController setStatus:[NSString stringWithFormat:@"Loaded key for %@", productName] duration:2.5]; + + return YES; +} + +- (BOOL)saveKeysForProduct:(NSString *)productName +{ + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *supportDir = [@"~/Library/Application Support/Aquatic" stringByExpandingTildeInPath]; + NSString *keyDir = [supportDir stringByAppendingString:@"/Product Keys"]; + NSString *productPath = [keyDir stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", productName]]; + BOOL isDir; + NSData *pubData; + NSData *privData; + unsigned char *pubBytes; + unsigned char *privBytes; + int pubLength = BN_num_bytes(rsaKey->n); + int privLength = BN_num_bytes(rsaKey->d); + + // Get the bytes for each key and copy the bytes + pubBytes = (unsigned char*)malloc(pubLength); + BN_bn2bin(rsaKey->n, pubBytes); + privBytes = (unsigned char*)malloc(privLength); + BN_bn2bin(rsaKey->d, privBytes); + + // Create the NSData objects + pubData = [NSData dataWithBytesNoCopy:pubBytes length:pubLength freeWhenDone:YES]; + privData = [NSData dataWithBytesNoCopy:privBytes length:privLength freeWhenDone:YES]; + + // Create the dictionary + NSDictionary *keyDict = [NSDictionary dictionaryWithObjectsAndKeys: pubData, @"Public Key", privData, @"Private Key", nil]; + + // The ~/Library/Application Support/Aquatic/ folder doesn't exist yet + if (![fm fileExistsAtPath:supportDir isDirectory:&isDir]) + { + // Create the ~/Library/Application Support/Aquatic/ directory + [fm createDirectoryAtPath:supportDir attributes:nil]; + } + // The support path leads to a file! Bad! + else if (!isDir) + { + NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"%@ already exists as a file. Can't create support directory.", supportDir], + @"OK", nil, nil); + return NO; + } + + if (![fm fileExistsAtPath:keyDir isDirectory:&isDir]) + { + // Create the product key directory + [fm createDirectoryAtPath:keyDir attributes:nil]; + } + // The key directory path leads to a file! Bad again! + else if (!isDir) + { + NSRunAlertPanel(@"Error", [NSString stringWithFormat:@"%@ already exists as a file. Can't create key storage directory.", keyDir], + @"OK", nil, nil); + return NO; + } + + [keyDict writeToFile:productPath atomically:YES]; + return YES; +} + +- (IBAction)exportKeys:(id)sender +{ + // Run the selection panel + NSSavePanel *savePanel = [NSSavePanel savePanel]; + [savePanel setPrompt:@"Export"]; + [savePanel setTitle:@"Export Keys To..."]; + if ([savePanel runModal] == NSFileHandlingPanelCancelButton) + return; + + NSString *exportPath = [savePanel filename]; + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *productPath = [[NSString stringWithFormat:@"~/Library/Application Support/Aquatic/Product Keys/%@.plist", + [productController currentProduct]] stringByExpandingTildeInPath]; + + [fm copyPath:productPath toPath:exportPath handler:nil]; +} + +- (IBAction)importKeys:(id)sender +{ + // Run the selection panel + NSOpenPanel *selectPanel = [NSOpenPanel openPanel]; + [selectPanel setCanChooseFiles:YES]; + [selectPanel setCanChooseDirectories:NO]; + [selectPanel setAllowsMultipleSelection:NO]; + [selectPanel setPrompt:@"Select"]; + [selectPanel setTitle:@"Select Key File"]; + if ([selectPanel runModal] == NSFileHandlingPanelCancelButton) + return; + + NSString *keyPath = [[selectPanel filenames] objectAtIndex:0]; + + NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:keyPath]; + if (![[dict allKeys] containsObject:@"Public Key"] || ![[dict allKeys] containsObject:@"Private Key"]) { + NSRunAlertPanel(@"Error", @"This file does not contain a proper keypair", @"OK", nil, nil); + return; + } + + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *productPath = [[NSString stringWithFormat:@"~/Library/Application Support/Aquatic/Product Keys/%@.plist", + [keyPath lastPathComponent]] stringByExpandingTildeInPath]; + + [fm copyPath:keyPath toPath:productPath handler:nil]; + [productController loadProducts]; +} + +@end diff --git a/Developer/Classes/LicenseController.h b/Developer/Classes/LicenseController.h new file mode 100644 index 0000000..3f321a2 --- /dev/null +++ b/Developer/Classes/LicenseController.h @@ -0,0 +1,61 @@ +// +// LicenseController.h +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import +@class AQTableView; +@class AquaticPrime; + +@interface LicenseController : NSObject +{ + IBOutlet AQTableView *keyValueTable; + IBOutlet NSButton *addButton; + IBOutlet NSButton *removeButton; + IBOutlet NSButton *generateLicenseButton; + IBOutlet NSButton *editExtensionButton; + IBOutlet NSButton *editSaveDirectoryButton; + IBOutlet NSTextField *licenseExtensionField; + IBOutlet NSTextField *saveDirectoryField; + IBOutlet NSTextField *licenseExtensionEditField; + IBOutlet NSPanel *licenseExtensionSheet; + + NSMutableArray *keyArray; + NSMutableArray *valueArray; + + AquaticPrime *licenseMaker; + + IBOutlet id keyController; + IBOutlet id statusController; + IBOutlet id productController; +} + +- (IBAction)generateLicense:(id)sender; +- (void)saveLicenseTemplate:(id)table; +- (BOOL)loadLicenseTemplate; +- (void)newProductSelected; +- (IBAction)editLicenseExtension:(id)sender; +- (IBAction)editSaveDirectory:(id)sender; + +@end diff --git a/Developer/Classes/LicenseController.m b/Developer/Classes/LicenseController.m new file mode 100644 index 0000000..39cd73f --- /dev/null +++ b/Developer/Classes/LicenseController.m @@ -0,0 +1,352 @@ +// +// LicenseController.m +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "LicenseController.h" +#import "KeyController.h" +#import "StatusController.h" +#import "ProductController.h" +#import "AquaticPrime.h" +#import "AQTableView.h" + +@implementation LicenseController + +#pragma mark Init + +- (id)init +{ + keyArray = [[NSMutableArray array] retain]; + valueArray = [[NSMutableArray array] retain]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(newProductSelected) name:@"ProductSelected" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveLicenseTemplate:) name:@"ProductWillBeSelected" object:nil]; + + return [super init]; +} + +- (void)dealloc +{ + [keyArray release]; + [valueArray release]; + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +#pragma mark License Generation + +- (IBAction)generateLicense:(id)sender +{ + NSString *errorString; + + if ([keyArray count] == 0) { + errorString = @"Please assign at least one key-value pair."; + NSRunAlertPanel(@"Could not create license file", errorString, @"OK", nil, nil); + return; + } + + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *supportDir = [@"~/Library/Application Support/Aquatic" stringByExpandingTildeInPath]; + NSString *licenseDir = [supportDir stringByAppendingString:@"/Generated Licenses"]; + BOOL isDir; + + // The ~/Library/Application Support/Aquatic/ folder doesn't exist yet + if (![fm fileExistsAtPath:supportDir isDirectory:&isDir]) + { + // The support path leads to a file! Bad! This shouldn't happen ever!! + if (!isDir) + [fm removeFileAtPath:supportDir handler:nil]; + + // Create the ~/Library/Application Support/Aquatic/ directory + [fm createDirectoryAtPath:supportDir attributes:nil]; + } + + // The ~/Library/Application Support/Aquatic/Generated Licenses folder doesn't exist yet + if (![fm fileExistsAtPath:licenseDir isDirectory:&isDir]) + { + // The licenses path leads to a file! Bad! This shouldn't happen ever!! + if (!isDir) + [fm removeFileAtPath:licenseDir handler:nil]; + + // Create the product key directory + [fm createDirectoryAtPath:licenseDir attributes:nil]; + } + + NSMutableDictionary *licenseDict = [NSMutableDictionary dictionaryWithObjects:valueArray forKeys:keyArray]; + + NSString *publicKey = [keyController publicKey]; + NSString *privateKey = [keyController privateKey]; + + if (!publicKey || !privateKey) + return; + + licenseMaker = [AquaticPrime aquaticPrimeWithKey:publicKey privateKey:privateKey]; + + NSString *path = [NSString stringWithFormat:@"%@/%@.%@", [saveDirectoryField stringValue], [valueArray objectAtIndex:0], [licenseExtensionField stringValue]]; + NSString *backupPath = [NSString stringWithFormat:@"%@/%@.%@", licenseDir, [valueArray objectAtIndex:0], [licenseExtensionField stringValue]]; + [licenseMaker writeLicenseFileForDictionary:licenseDict toPath:path]; + [licenseMaker writeLicenseFileForDictionary:licenseDict toPath:backupPath]; + [statusController setStatus:[NSString stringWithFormat:@"Wrote license to %@", path] duration:2.5]; +} + +#pragma mark Saving & Loading License Templates + +- (void)saveLicenseTemplate:(id)anObject +{ + // Make sure the object is a tableView + int index; + if ([anObject respondsToSelector:@selector(object)] && [[anObject object] respondsToSelector:@selector(selectedRow)]) + index = [[anObject object] selectedRow]; + else + return; + + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *supportDir = [@"~/Library/Application Support/Aquatic" stringByExpandingTildeInPath]; + NSString *templateDir = [supportDir stringByAppendingString:@"/License Templates"]; + NSString *productPath = [templateDir stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", [productController productAtIndex:index]]]; + BOOL isDir; + + // Save this way to preserve the order of items + NSDictionary *templateDict = [NSDictionary dictionaryWithObjectsAndKeys:keyArray, @"Keys", + valueArray, @"Values", + [licenseExtensionField stringValue], @"Extension", + [saveDirectoryField stringValue], @"Save Directory", nil]; + + // The ~/Library/Application Support/Aquatic/ folder doesn't exist yet + if (![fm fileExistsAtPath:supportDir isDirectory:&isDir]) + { + // The support path leads to a file! Bad! This shouldn't happen ever!! + if (!isDir) + [fm removeFileAtPath:supportDir handler:nil]; + + // Create the ~/Library/Application Support/Aquatic/ directory + [fm createDirectoryAtPath:supportDir attributes:nil]; + } + + // The ~/Library/Application Support/Aquatic/License Templates folder doesn't exist yet + if (![fm fileExistsAtPath:templateDir isDirectory:&isDir]) + { + // The template path leads to a file! Bad! This shouldn't happen ever!! + if (!isDir) + [fm removeFileAtPath:templateDir handler:nil]; + + // Create the product key directory + [fm createDirectoryAtPath:templateDir attributes:nil]; + } + + [templateDict writeToFile:productPath atomically:YES]; + + return; +} + +- (BOOL)loadLicenseTemplate +{ + if (![productController currentProduct]) + return NO; + + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *supportDir = [@"~/Library/Application Support/Aquatic" stringByExpandingTildeInPath]; + NSString *templateDir = [supportDir stringByAppendingString:@"/License Templates"]; + NSString *productPath = [templateDir stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", [productController currentProduct]]]; + + if (![fm fileExistsAtPath:productPath]) + { + [licenseExtensionField setStringValue:@"plist"]; + [licenseExtensionEditField setStringValue:@"plist"]; + [saveDirectoryField setStringValue:[@"~/Documents" stringByExpandingTildeInPath]]; + return NO; + } + + NSDictionary *templateDict = [NSDictionary dictionaryWithContentsOfFile:productPath]; + keyArray = [[NSMutableArray arrayWithArray:[templateDict objectForKey:@"Keys"]] retain]; + valueArray = [[NSMutableArray arrayWithArray:[templateDict objectForKey:@"Values"]] retain]; + [licenseExtensionField setStringValue:[templateDict objectForKey:@"Extension"]]; + [saveDirectoryField setStringValue:[templateDict objectForKey:@"Save Directory"]]; + + if (![keyArray count]) + [generateLicenseButton setEnabled:NO]; + + return YES; +} + +#pragma mark Product Selection + +- (void)newProductSelected +{ + if (keyArray) + [keyArray release]; + if (valueArray) + [valueArray release]; + + // Enable everything except the remove button + [addButton setEnabled:YES]; + [generateLicenseButton setEnabled:YES]; + [editExtensionButton setEnabled:YES]; + [editSaveDirectoryButton setEnabled:YES]; + + // No template and a product, i.e. new product + if (![self loadLicenseTemplate] && [productController currentProduct]) { + // Default values + keyArray = [[NSMutableArray arrayWithObjects:@"Name", @"Email", nil] retain]; + valueArray = [[NSMutableArray arrayWithObjects:@"User", @"user@email.com", nil] retain]; + } + // No product + else if (![productController currentProduct] ) { + // Disable everything + [addButton setEnabled:NO]; + [removeButton setEnabled:NO]; + [generateLicenseButton setEnabled:NO]; + [editExtensionButton setEnabled:NO]; + [editSaveDirectoryButton setEnabled:NO]; + + [licenseExtensionField setStringValue:@""]; + [saveDirectoryField setStringValue:@""]; + + keyArray = [[NSMutableArray array] retain]; + valueArray = [[NSMutableArray array] retain]; + } + + [keyValueTable reloadData]; +} + +#pragma mark Interface + +- (IBAction)editLicenseExtension:(id)sender +{ + [licenseExtensionEditField setStringValue:[licenseExtensionField stringValue]]; + [NSApp beginSheet:licenseExtensionSheet modalForWindow:[NSApp keyWindow] + modalDelegate:self didEndSelector:nil contextInfo:nil]; +} + +- (IBAction)editSaveDirectory:(id)sender +{ + // Run the selection panel + NSOpenPanel *selectPanel = [NSOpenPanel openPanel]; + [selectPanel setCanChooseFiles:NO]; + [selectPanel setCanChooseDirectories:YES]; + [selectPanel setAllowsMultipleSelection:NO]; + [selectPanel setPrompt:@"Select"]; + [selectPanel setTitle:@"Select Directory"]; + if ([selectPanel runModal] == NSFileHandlingPanelCancelButton) + return; + + [saveDirectoryField setStringValue:[[selectPanel filenames] objectAtIndex:0]]; +} + +- (IBAction)sheetOK:(id)sender +{ + [NSApp endSheet:licenseExtensionSheet]; + [licenseExtensionSheet orderOut:self]; + + [licenseExtensionField setStringValue:[licenseExtensionEditField stringValue]]; +} + +- (IBAction)sheetCancel:(id)sender +{ + [NSApp endSheet:licenseExtensionSheet]; + [licenseExtensionSheet orderOut:self]; +} + +- (IBAction)addKeyValue:(id)sender +{ + [keyArray addObject:@"New Key"]; + [valueArray addObject:@"Undefined"]; + + [keyValueTable reloadData]; + + [generateLicenseButton setEnabled:YES]; +} + +- (IBAction)removeKeyValue:(id)sender +{ + if ([keyValueTable selectedRow] == -1) + return; + else + [keyValueTable deleteItemAtIndex:[keyValueTable selectedRow]]; +} + +#pragma mark TableView Delegate Methods + +- (void)deleteItemAtIndex:(int)row +{ + [keyArray removeObjectAtIndex:row]; + [valueArray removeObjectAtIndex:row]; + + // Make sure we don't lose a reference to the arrays + if (![keyArray count]) + [keyArray retain]; + if (![valueArray count]) + [valueArray retain]; + + [keyValueTable reloadData]; + [keyValueTable selectRow:row-1 byExtendingSelection:NO]; + + if (![keyArray count]) + [generateLicenseButton setEnabled:NO]; +} + +- (int)numberOfRowsInTableView:(NSTableView *)tableView +{ + if ([keyArray count] <= [valueArray count]) + return [keyArray count]; + else + return [valueArray count]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row +{ + if ([[tableColumn identifier] isEqualToString:@"keyColumn"]) + return [keyArray objectAtIndex:row]; + else + return [valueArray objectAtIndex:row]; +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row +{ + if (!object) + return; + + if ([[tableColumn identifier] isEqualToString:@"keyColumn"] && [object isEqualToString:@"Signature"]) { + NSRunAlertPanel(@"Signature is a reserved key-value pair", @"Please choose another key name.", @"OK", nil, nil); + return; + } + + if ([[tableColumn identifier] isEqualToString:@"keyColumn"]) + [keyArray replaceObjectAtIndex:row withObject:object]; + else + [valueArray replaceObjectAtIndex:row withObject:object]; + + [keyValueTable reloadData]; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification +{ + if ([keyValueTable selectedRow] == -1) + [removeButton setEnabled:NO]; + else + [removeButton setEnabled:YES]; +} + +@end diff --git a/Developer/Classes/MainController.h b/Developer/Classes/MainController.h new file mode 100644 index 0000000..0fd836f --- /dev/null +++ b/Developer/Classes/MainController.h @@ -0,0 +1,17 @@ +/* MainController */ + +#import +@class InfoController; +@class AboutController; + +@interface MainController : NSObject +{ + NSWindow *mainWindow; + InfoController *infoController; + AboutController *aboutController; +} + +- (IBAction)showLicenseInfoWindow:(id)sender; +- (IBAction)closeWindow:(id)sender; + +@end diff --git a/Developer/Classes/MainController.m b/Developer/Classes/MainController.m new file mode 100644 index 0000000..bd9212f --- /dev/null +++ b/Developer/Classes/MainController.m @@ -0,0 +1,55 @@ +#import "MainController.h" +#import "InfoController.h" +#import "AboutController.h" + +@implementation MainController + +- (void)awakeFromNib +{ + [mainWindow makeKeyAndOrderFront:self]; +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication +{ + return YES; +} + +- (void)windowWillClose:(NSNotification *)aNotification +{ + if ([[[aNotification object] frameAutosaveName] isEqualToString:@"mainWindow"]) + [NSApp terminate:self]; +} + +- (IBAction)showAboutBox:(id)sender +{ + if (!aboutController) { + aboutController = [[AboutController alloc] init]; + } + + [[aboutController window] makeKeyAndOrderFront:self]; +} + +- (IBAction)showLicenseInfoWindow:(id)sender +{ + if (!infoController) { + infoController = [[InfoController alloc] init]; + } + [infoController showWindow:self]; +} + +- (IBAction)closeWindow:(id)sender +{ + [[NSApp keyWindow] orderOut:self]; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)aMenuItem +{ + if ([[aMenuItem title] isEqualToString:@"Close"]) { + if (![NSApp keyWindow]) + return NO; + } + + return YES; +} + +@end diff --git a/Developer/Classes/ProductController.h b/Developer/Classes/ProductController.h new file mode 100644 index 0000000..cf356c8 --- /dev/null +++ b/Developer/Classes/ProductController.h @@ -0,0 +1,53 @@ +// +// ProductController.h +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import +@class AQGradientTableView; + +@interface ProductController : NSObject +{ + IBOutlet AQGradientTableView *productTable; + IBOutlet NSButton *cancelButton; + IBOutlet NSButton *saveButton; + IBOutlet NSButton *removeButton; + IBOutlet NSWindow *mainWindow; + IBOutlet NSWindow *newProductSheet; + IBOutlet NSTextField *nameField; + IBOutlet NSMenu *productMenu; + + NSMutableArray *productArray; + + IBOutlet id keyController; +} + +- (void)loadProducts; +- (IBAction)addNewProduct:(id)sender; +- (IBAction)removeProduct:(id)sender; +- (NSString *)currentProduct; +- (NSString *)productAtIndex:(int)index; +- (NSArray*)allProducts; + +@end diff --git a/Developer/Classes/ProductController.m b/Developer/Classes/ProductController.m new file mode 100644 index 0000000..662ab2b --- /dev/null +++ b/Developer/Classes/ProductController.m @@ -0,0 +1,283 @@ +// +// ProductController.m +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "ProductController.h" +#import "KeyController.h" +#import "AQTableView.h" + +@implementation ProductController + +#pragma mark Init + +- (id)init +{ + productArray = [[[NSMutableArray alloc] init] retain]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(loadProducts) name:@"NewKeyGenerated" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveProducts:) name:@"NSApplicationWillTerminateNotification" object:nil]; + return [super init]; +} + +- (void)dealloc +{ + [productArray release]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (void)awakeFromNib +{ + [self loadProducts]; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)aMenuItem +{ + if ([[aMenuItem title] isEqualToString:@"Duplicate"] || [[aMenuItem title] isEqualToString:@"Rename"]) { + if (![productArray count]) + return NO; + } + + return YES; +} + +- (IBAction)saveProducts:(id)sender +{ + [[NSNotificationCenter defaultCenter] postNotificationName:@"ProductWillBeSelected" object:productTable]; +} + +#pragma mark Load Project + +- (void)loadProducts +{ + NSString *keyDir = [@"~/Library/Application Support/Aquatic/Product Keys" stringByExpandingTildeInPath]; + NSString *curPath; + NSMutableArray *possibleproductArray = [NSMutableArray array]; + NSDirectoryEnumerator *pathEnum = [[NSFileManager defaultManager] enumeratorAtPath:keyDir]; + + // Grab all the key paths + if(pathEnum) { + while ((curPath = [pathEnum nextObject])) { + if ([[curPath pathExtension] isEqualToString:@"plist"]) + [possibleproductArray addObject:[keyDir stringByAppendingPathComponent:curPath]]; + } + } + + // Determine if they are real keys + NSEnumerator *keyEnum = [possibleproductArray objectEnumerator]; + NSString *curKey; + NSDictionary *testDict; + + while ((curKey = [keyEnum nextObject])) { + testDict = [NSDictionary dictionaryWithContentsOfFile:curKey]; + // If it has both public and private key, add it to the product list + if ([[testDict allKeys] containsObject:@"Public Key"] && [[testDict allKeys] containsObject:@"Private Key"]) { + if (![productArray containsObject:[[curKey lastPathComponent] stringByDeletingPathExtension]]) + [productArray addObject:[[curKey lastPathComponent] stringByDeletingPathExtension]]; + } + } + + [productArray sortUsingSelector:@selector(caseInsensitiveCompare:)]; + [productTable reloadData]; + + if ([productArray count]) { + [[NSNotificationCenter defaultCenter] postNotificationName:@"ProductSelected" object:nil]; + [removeButton setEnabled:YES]; + } + else { + [removeButton setEnabled:NO]; + } +} + +#pragma mark Add Project + +- (IBAction)addNewProduct:(id)sender +{ + [nameField setStringValue:@"Untitled"]; + [NSApp beginSheet:newProductSheet modalForWindow:mainWindow modalDelegate:self didEndSelector:nil contextInfo:nil]; +} + +- (IBAction)duplicateProduct:(id)sender +{ + if (![self currentProduct]) + return; + + // This saves the current license template + [self saveProducts:self]; + + // Copy the files + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *oldProduct = [self currentProduct]; + + // Figure out which copy we are on + NSString *copy = @" Copy"; + int i = 1; + while ([productArray containsObject:[oldProduct stringByAppendingString:copy]]) + copy = [NSString stringWithFormat:@" Copy %i", i++]; + + NSString *oldTemplateProductPath = [[@"~/Library/Application Support/Aquatic/License Templates" stringByExpandingTildeInPath] stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", oldProduct]]; + NSString *newTemplateProductPath = [[@"~/Library/Application Support/Aquatic/License Templates" stringByExpandingTildeInPath] stringByAppendingString:[NSString stringWithFormat:@"/%@%@.plist", oldProduct, copy]]; + NSString *oldKeyProductPath = [[@"~/Library/Application Support/Aquatic/Product Keys" stringByExpandingTildeInPath] stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", oldProduct]]; + NSString *newKeyProductPath = [[@"~/Library/Application Support/Aquatic/Product Keys" stringByExpandingTildeInPath] stringByAppendingString:[NSString stringWithFormat:@"/%@%@.plist", oldProduct, copy]]; + + [fm copyPath:oldTemplateProductPath toPath:newTemplateProductPath handler:nil]; + [fm copyPath:oldKeyProductPath toPath:newKeyProductPath handler:nil]; + + [productArray addObject:[oldProduct stringByAppendingString:copy]]; + [productArray sortUsingSelector:@selector(caseInsensitiveCompare:)]; + [productTable reloadData]; + // Select the copy + [productTable selectRow:[productArray indexOfObject:[oldProduct stringByAppendingString:copy]] byExtendingSelection:NO]; +} + +- (IBAction)sheetOK:(id)sender +{ + [NSApp endSheet:newProductSheet]; + [newProductSheet orderOut:self]; + + NSString *productName = [nameField stringValue]; + + if ([productName isEqualToString:@""]) + return; + + [keyController generateKeyForProduct:productName]; + if ([productArray count]) { + [productTable selectRow:[productArray indexOfObject:productName] byExtendingSelection:NO]; + [removeButton setEnabled:YES]; + } +} + +- (IBAction)sheetCancel:(id)sender +{ + [NSApp endSheet:newProductSheet]; + [newProductSheet orderOut:self]; + [nameField setStringValue:@""]; +} + +#pragma mark Remove Project + +- (IBAction)removeProduct:(id)sender +{ + if ([productTable selectedRow] != -1) + [productTable deleteItemAtIndex:[productTable selectedRow]]; + + if (![productArray count]) + [removeButton setEnabled:NO]; +} + +#pragma mark Product Names + +- (NSArray*)allProducts +{ + return productArray; +} + +- (NSString *)currentProduct +{ + int index = [productTable selectedRow]; + + if (index == -1) + return nil; + else + return [productArray objectAtIndex:index]; +} + +- (NSString *)productAtIndex:(int)index +{ + if (index == -1) + return nil; + else + return [productArray objectAtIndex:index]; +} + +- (IBAction)renameProduct:(id)sender +{ + [productTable editColumn:0 row:[productTable selectedRow] withEvent:nil select:YES]; +} + +#pragma mark TableView Delegate Methods + +- (void)deleteItemAtIndex:(int)index +{ + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *supportDir = [@"~/Library/Application Support/Aquatic" stringByExpandingTildeInPath]; + NSString *product = [self productAtIndex:index]; + + if (NSRunAlertPanel([NSString stringWithFormat:@"Are you sure you want to delete %@?", product], + @"This cannot be undone.", @"OK", @"Cancel", nil) == NSAlertAlternateReturn) + return; + + [fm removeFileAtPath:[supportDir stringByAppendingString:[NSString stringWithFormat:@"/Product Keys/%@.plist", product]] handler:nil]; + [fm removeFileAtPath:[supportDir stringByAppendingString:[NSString stringWithFormat:@"/License Templates/%@.plist", product]] handler:nil]; + [productArray removeObjectAtIndex:index]; + + [productTable reloadData]; + [productTable selectRow:index-1 byExtendingSelection:NO]; + //[[NSNotificationCenter defaultCenter] postNotificationName:@"ProductSelected" object:productTable]; +} + +- (int)numberOfRowsInTableView:(NSTableView *)tableView +{ + return [productArray count]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row +{ + return [productArray objectAtIndex:row]; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification +{ + [[NSNotificationCenter defaultCenter] postNotificationName:@"ProductSelected" object:aNotification]; +} + +- (BOOL)selectionShouldChangeInTableView:(NSTableView *)tableView +{ + [[NSNotificationCenter defaultCenter] postNotificationName:@"ProductWillBeSelected" object:tableView]; + return YES; +} + +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(int)row +{ + // Don't allow "" as a name + if (!object || [object isEqualToString:@""]) + return; + + // Move the keys to the new path + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *oldProductPath = [[@"~/Library/Application Support/Aquatic/Product Keys" stringByExpandingTildeInPath] + stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", [productArray objectAtIndex:row]]]; + + NSString *newProductPath = [[@"~/Library/Application Support/Aquatic/Product Keys" stringByExpandingTildeInPath] + stringByAppendingString:[NSString stringWithFormat:@"/%@.plist", object]]; + + [fm movePath:oldProductPath toPath:newProductPath handler:nil]; + + // Change the name + [productArray replaceObjectAtIndex:row withObject:object]; + [productArray sortUsingSelector:@selector(caseInsensitiveCompare:)]; + [productTable reloadData]; +} + +@end diff --git a/Developer/Classes/StatusController.h b/Developer/Classes/StatusController.h new file mode 100644 index 0000000..85a5af2 --- /dev/null +++ b/Developer/Classes/StatusController.h @@ -0,0 +1,38 @@ +// +// StatusController.h +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import + +@interface StatusController : NSObject +{ + IBOutlet NSTextField *statusField; + NSTimer *statusTimer; + int timerInUse; +} + +- (void)setStatus:(NSString *)status duration:(float)seconds; + +@end diff --git a/Developer/Classes/StatusController.m b/Developer/Classes/StatusController.m new file mode 100644 index 0000000..db4580c --- /dev/null +++ b/Developer/Classes/StatusController.m @@ -0,0 +1,53 @@ +// +// StatusController.m +// AquaticPrime Developer +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "StatusController.h" + +@implementation StatusController + +- (void)awakeFromNib +{ + timerInUse = 0; +} + +- (void)setStatus:(NSString *)status duration:(float)seconds +{ + if (timerInUse && [statusTimer isValid]) + [statusTimer invalidate]; + + [statusField setStringValue:status]; + statusTimer = [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(clearStatusField) userInfo:nil repeats:NO]; + timerInUse = 1; +} + +- (void)clearStatusField +{ + [statusField setStringValue:@""]; + [statusTimer invalidate]; + timerInUse = 0; +} + +@end diff --git a/Developer/English.lproj/InfoPlist.strings b/Developer/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..1e10629 Binary files /dev/null and b/Developer/English.lproj/InfoPlist.strings differ diff --git a/Developer/English.lproj/MainMenu.nib/classes.nib b/Developer/English.lproj/MainMenu.nib/classes.nib new file mode 100644 index 0000000..3ff272a --- /dev/null +++ b/Developer/English.lproj/MainMenu.nib/classes.nib @@ -0,0 +1,92 @@ +{ + IBClasses = ( + {CLASS = AQTableView; LANGUAGE = ObjC; SUPERCLASS = NSTableView; }, + {CLASS = AQTextFieldCell; LANGUAGE = ObjC; SUPERCLASS = NSTextFieldCell; }, + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + ACTIONS = {exportKeys = id; generateKey = id; importKeys = id; }; + CLASS = KeyController; + LANGUAGE = ObjC; + OUTLETS = { + generateButton = NSButton; + privateKeyView = NSTextView; + productController = id; + publicKeyView = NSTextView; + rsaKeyView = NSTextView; + statusController = id; + tabView = NSTabView; + }; + SUPERCLASS = NSObject; + }, + { + ACTIONS = { + addKeyValue = id; + editLicenseExtension = id; + editSaveDirectory = id; + generateLicense = id; + removeKeyValue = id; + saveLicenseTemplate = id; + sheetCancel = id; + sheetOK = id; + }; + CLASS = LicenseController; + LANGUAGE = ObjC; + OUTLETS = { + addButton = NSButton; + editExtensionButton = NSButton; + editSaveDirectoryButton = NSButton; + generateLicenseButton = NSButton; + keyController = id; + keyValueTable = NSTableView; + licenseExtensionEditField = NSTextField; + licenseExtensionField = NSTextField; + licenseExtensionSheet = NSPanel; + productController = id; + removeButton = NSButton; + saveDirectoryField = NSTextField; + statusController = id; + }; + SUPERCLASS = NSObject; + }, + { + ACTIONS = {closeWindow = id; showAboutBox = id; showLicenseInfoWindow = id; }; + CLASS = MainController; + LANGUAGE = ObjC; + OUTLETS = {mainWindow = NSWindow; }; + SUPERCLASS = NSObject; + }, + {CLASS = OAGradientTableView; LANGUAGE = ObjC; SUPERCLASS = NSTableView; }, + { + ACTIONS = { + addNewProduct = id; + duplicateProduct = id; + removeProduct = id; + renameProduct = id; + saveProducts = id; + sheetCancel = id; + sheetOK = id; + }; + CLASS = ProductController; + LANGUAGE = ObjC; + OUTLETS = { + cancelButton = NSButton; + keyController = id; + mainWindow = NSWindow; + nameField = NSTextField; + newProductSheet = NSPanel; + productTable = NSTableView; + removeButton = NSButton; + saveButton = NSButton; + }; + SUPERCLASS = NSObject; + }, + {CLASS = ScriptingDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + CLASS = StatusController; + LANGUAGE = ObjC; + OUTLETS = {statusField = NSTextField; }; + SUPERCLASS = NSObject; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Developer/English.lproj/MainMenu.nib/info.nib b/Developer/English.lproj/MainMenu.nib/info.nib new file mode 100644 index 0000000..5ac1b93 --- /dev/null +++ b/Developer/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,32 @@ + + + + + IBDocumentLocation + 381 32 356 240 0 0 1440 938 + IBEditorPositions + + 29 + 50 869 452 44 0 0 1440 938 + 486 + 29 267 130 99 0 0 1280 1002 + + IBFramework Version + 443.0 + IBLockedObjects + + 264 + + IBLockedTabItems + + 267 + + IBOpenObjects + + 29 + 258 + + IBSystem Version + 8F46 + + diff --git a/Developer/English.lproj/MainMenu.nib/keyedobjects.nib b/Developer/English.lproj/MainMenu.nib/keyedobjects.nib new file mode 100644 index 0000000..049325c Binary files /dev/null and b/Developer/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/Developer/English.lproj/MainMenu~.nib/classes.nib b/Developer/English.lproj/MainMenu~.nib/classes.nib new file mode 100644 index 0000000..3ff272a --- /dev/null +++ b/Developer/English.lproj/MainMenu~.nib/classes.nib @@ -0,0 +1,92 @@ +{ + IBClasses = ( + {CLASS = AQTableView; LANGUAGE = ObjC; SUPERCLASS = NSTableView; }, + {CLASS = AQTextFieldCell; LANGUAGE = ObjC; SUPERCLASS = NSTextFieldCell; }, + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + ACTIONS = {exportKeys = id; generateKey = id; importKeys = id; }; + CLASS = KeyController; + LANGUAGE = ObjC; + OUTLETS = { + generateButton = NSButton; + privateKeyView = NSTextView; + productController = id; + publicKeyView = NSTextView; + rsaKeyView = NSTextView; + statusController = id; + tabView = NSTabView; + }; + SUPERCLASS = NSObject; + }, + { + ACTIONS = { + addKeyValue = id; + editLicenseExtension = id; + editSaveDirectory = id; + generateLicense = id; + removeKeyValue = id; + saveLicenseTemplate = id; + sheetCancel = id; + sheetOK = id; + }; + CLASS = LicenseController; + LANGUAGE = ObjC; + OUTLETS = { + addButton = NSButton; + editExtensionButton = NSButton; + editSaveDirectoryButton = NSButton; + generateLicenseButton = NSButton; + keyController = id; + keyValueTable = NSTableView; + licenseExtensionEditField = NSTextField; + licenseExtensionField = NSTextField; + licenseExtensionSheet = NSPanel; + productController = id; + removeButton = NSButton; + saveDirectoryField = NSTextField; + statusController = id; + }; + SUPERCLASS = NSObject; + }, + { + ACTIONS = {closeWindow = id; showAboutBox = id; showLicenseInfoWindow = id; }; + CLASS = MainController; + LANGUAGE = ObjC; + OUTLETS = {mainWindow = NSWindow; }; + SUPERCLASS = NSObject; + }, + {CLASS = OAGradientTableView; LANGUAGE = ObjC; SUPERCLASS = NSTableView; }, + { + ACTIONS = { + addNewProduct = id; + duplicateProduct = id; + removeProduct = id; + renameProduct = id; + saveProducts = id; + sheetCancel = id; + sheetOK = id; + }; + CLASS = ProductController; + LANGUAGE = ObjC; + OUTLETS = { + cancelButton = NSButton; + keyController = id; + mainWindow = NSWindow; + nameField = NSTextField; + newProductSheet = NSPanel; + productTable = NSTableView; + removeButton = NSButton; + saveButton = NSButton; + }; + SUPERCLASS = NSObject; + }, + {CLASS = ScriptingDelegate; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + CLASS = StatusController; + LANGUAGE = ObjC; + OUTLETS = {statusField = NSTextField; }; + SUPERCLASS = NSObject; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Developer/English.lproj/MainMenu~.nib/info.nib b/Developer/English.lproj/MainMenu~.nib/info.nib new file mode 100644 index 0000000..5ac1b93 --- /dev/null +++ b/Developer/English.lproj/MainMenu~.nib/info.nib @@ -0,0 +1,32 @@ + + + + + IBDocumentLocation + 381 32 356 240 0 0 1440 938 + IBEditorPositions + + 29 + 50 869 452 44 0 0 1440 938 + 486 + 29 267 130 99 0 0 1280 1002 + + IBFramework Version + 443.0 + IBLockedObjects + + 264 + + IBLockedTabItems + + 267 + + IBOpenObjects + + 29 + 258 + + IBSystem Version + 8F46 + + diff --git a/Developer/English.lproj/MainMenu~.nib/keyedobjects.nib b/Developer/English.lproj/MainMenu~.nib/keyedobjects.nib new file mode 100644 index 0000000..049325c Binary files /dev/null and b/Developer/English.lproj/MainMenu~.nib/keyedobjects.nib differ diff --git a/Developer/Images/aboutbox.png b/Developer/Images/aboutbox.png new file mode 100644 index 0000000..fb57cac Binary files /dev/null and b/Developer/Images/aboutbox.png differ diff --git a/Developer/Images/add.tiff b/Developer/Images/add.tiff new file mode 100644 index 0000000..b78b0c2 Binary files /dev/null and b/Developer/Images/add.tiff differ diff --git a/Developer/Images/aquatic.icns b/Developer/Images/aquatic.icns new file mode 100644 index 0000000..0cddd14 Binary files /dev/null and b/Developer/Images/aquatic.icns differ diff --git a/Developer/Images/badge.icns b/Developer/Images/badge.icns new file mode 100644 index 0000000..57869b5 Binary files /dev/null and b/Developer/Images/badge.icns differ diff --git a/Developer/Images/locked.tiff b/Developer/Images/locked.tiff new file mode 100644 index 0000000..f99071e Binary files /dev/null and b/Developer/Images/locked.tiff differ diff --git a/Developer/Images/remove.tiff b/Developer/Images/remove.tiff new file mode 100644 index 0000000..dacc97c Binary files /dev/null and b/Developer/Images/remove.tiff differ diff --git a/Developer/Images/unlocked.tiff b/Developer/Images/unlocked.tiff new file mode 100644 index 0000000..920c4bf Binary files /dev/null and b/Developer/Images/unlocked.tiff differ diff --git a/Developer/Info.plist b/Developer/Info.plist new file mode 100644 index 0000000..df49276 --- /dev/null +++ b/Developer/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + AquaticPrime Developer + CFBundleGetInfoString + AquaticPrime Developer 1.0.4, © 2005 Aquatic + CFBundleIconFile + badge + CFBundleIdentifier + com.aquaticmac.aquaticPrimeDeveloper + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + aqat + CFBundleVersion + 1.0.4 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + SmartCrashReports_CompanyName + Aquatic + SmartCrashReports_EmailTicket + SCR-0562EF72C0 + + diff --git a/Developer/Nibs/About.nib/classes.nib b/Developer/Nibs/About.nib/classes.nib new file mode 100644 index 0000000..a6442f0 --- /dev/null +++ b/Developer/Nibs/About.nib/classes.nib @@ -0,0 +1,9 @@ +{ + IBClasses = ( + {CLASS = AQAboutView; LANGUAGE = ObjC; SUPERCLASS = NSView; }, + {CLASS = AQAboutWindow; LANGUAGE = ObjC; SUPERCLASS = NSWindow; }, + {CLASS = AboutController; LANGUAGE = ObjC; SUPERCLASS = NSWindowController; }, + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Developer/Nibs/About.nib/info.nib b/Developer/Nibs/About.nib/info.nib new file mode 100644 index 0000000..2b89113 --- /dev/null +++ b/Developer/Nibs/About.nib/info.nib @@ -0,0 +1,20 @@ + + + + + IBDocumentLocation + 423 39 356 240 0 0 1280 1002 + IBFramework Version + 439.0 + IBLockedObjects + + 8 + + IBOpenObjects + + 5 + + IBSystem Version + 8B15 + + diff --git a/Developer/Nibs/About.nib/keyedobjects.nib b/Developer/Nibs/About.nib/keyedobjects.nib new file mode 100644 index 0000000..058ee1f Binary files /dev/null and b/Developer/Nibs/About.nib/keyedobjects.nib differ diff --git a/Developer/Nibs/LicenseInfo.nib/classes.nib b/Developer/Nibs/LicenseInfo.nib/classes.nib new file mode 100644 index 0000000..24290df --- /dev/null +++ b/Developer/Nibs/LicenseInfo.nib/classes.nib @@ -0,0 +1,18 @@ +{ + IBClasses = ( + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + CLASS = InfoController; + LANGUAGE = ObjC; + OUTLETS = { + hashField = NSTextField; + keyValueInfoTable = NSTableView; + licenseInfoView = NSView; + licenseValidField = NSTextField; + licenseWindow = NSWindow; + }; + SUPERCLASS = NSWindowController; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Developer/Nibs/LicenseInfo.nib/info.nib b/Developer/Nibs/LicenseInfo.nib/info.nib new file mode 100644 index 0000000..e35197c --- /dev/null +++ b/Developer/Nibs/LicenseInfo.nib/info.nib @@ -0,0 +1,22 @@ + + + + + IBDocumentLocation + 44 39 356 240 0 0 1280 1002 + IBEditorPositions + + 8 + 401 499 477 362 0 0 1280 1002 + + IBFramework Version + 439.0 + IBOpenObjects + + 5 + 8 + + IBSystem Version + 8C46 + + diff --git a/Developer/Nibs/LicenseInfo.nib/keyedobjects.nib b/Developer/Nibs/LicenseInfo.nib/keyedobjects.nib new file mode 100644 index 0000000..a4b4f0e Binary files /dev/null and b/Developer/Nibs/LicenseInfo.nib/keyedobjects.nib differ diff --git a/Developer/Nibs/LicenseInfo~.nib/classes.nib b/Developer/Nibs/LicenseInfo~.nib/classes.nib new file mode 100644 index 0000000..b3fb772 --- /dev/null +++ b/Developer/Nibs/LicenseInfo~.nib/classes.nib @@ -0,0 +1,18 @@ +{ + IBClasses = ( + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + CLASS = InfoController; + LANGUAGE = ObjC; + OUTLETS = { + hashField = NSTextField; + keyValueInfoTable = NSTableView; + licenseInfoView = NSView; + licenseValidField = NSWindow; + licenseWindow = NSTextField; + }; + SUPERCLASS = NSWindowController; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/Developer/Nibs/LicenseInfo~.nib/info.nib b/Developer/Nibs/LicenseInfo~.nib/info.nib new file mode 100644 index 0000000..e35197c --- /dev/null +++ b/Developer/Nibs/LicenseInfo~.nib/info.nib @@ -0,0 +1,22 @@ + + + + + IBDocumentLocation + 44 39 356 240 0 0 1280 1002 + IBEditorPositions + + 8 + 401 499 477 362 0 0 1280 1002 + + IBFramework Version + 439.0 + IBOpenObjects + + 5 + 8 + + IBSystem Version + 8C46 + + diff --git a/Developer/Nibs/LicenseInfo~.nib/keyedobjects.nib b/Developer/Nibs/LicenseInfo~.nib/keyedobjects.nib new file mode 100644 index 0000000..8cda8ee Binary files /dev/null and b/Developer/Nibs/LicenseInfo~.nib/keyedobjects.nib differ diff --git a/Developer/libAquaticPrime.a b/Developer/libAquaticPrime.a new file mode 100644 index 0000000..2301645 Binary files /dev/null and b/Developer/libAquaticPrime.a differ diff --git a/Developer/main.m b/Developer/main.m new file mode 100644 index 0000000..6b258ff --- /dev/null +++ b/Developer/main.m @@ -0,0 +1,14 @@ +// +// main.m +// RSA Helper +// +// Created by lucas on 5/12/05. +// Copyright __MyCompanyName__ 2005. All rights reserved. +// + +#import + +int main(int argc, char *argv[]) +{ + return NSApplicationMain(argc, (const char **) argv); +} diff --git a/README b/README new file mode 100644 index 0000000..90a01b5 --- /dev/null +++ b/README @@ -0,0 +1,8 @@ +What is it? +The AquaticPrime framework is a secure registration method for your shareware applications, released as free open-source software. + +How is this possible? +AquaticPrime uses RSA encryption to provide fairly good security - the same that is used to protect government documents. It is computationally infeasible for an attacker to generate fake serial numbers, despite the entire framework being open-source. + +Why did you write this? +I wrote this framework because I dislike several aspects of the registration methods provided by commercial companies. It seems to me that they provide proprietary, insecure algorithms and then tack on obnoxious activation schemes if you want any real security. \ No newline at end of file diff --git a/User/C#/AquaticPrime.cs b/User/C#/AquaticPrime.cs new file mode 100644 index 0000000..6f922f7 --- /dev/null +++ b/User/C#/AquaticPrime.cs @@ -0,0 +1,153 @@ +/* + AquaticPrime.cs + AquaticPrime Framework + + Copyright (c) 2008, Kyle Kinkade + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + •Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + •Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + •Neither the name of Aquatic nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Xml; +using System.Text; +using System.Collections; +using System.Globalization; +using System.Security.Cryptography; +using System.Text.RegularExpressions; + +namespace AquaticPrime +{ + sealed class AquaticPrime + { + + private RSACryptoServiceProvider rsa; + private string publicKey; + + /* + *This is currently implemented as a singleton, remove both GetInstance and instance + *if you don't want this class as a singleton + */ + private static readonly BGRegistrationCenter instance = new BGRegistrationCenter(); + public static BGRegistrationCenter GetInstance() + { + return instance; + } + + public bool VerifyLicenseData(XmlDocument xmlDoc) + { + try + { + //if publicKey is Hex, convert to Base64 + if(isHex(publicKey)) + publicKey = Hex2B64(publicKey); + + RSAParameters rsp = new RSAParameters(); + + //set publicKey + rsp.Modulus = Convert.FromBase64String(publicKey); + + //we know that the exponent is supposed to be 3 + rsp.Exponent = Convert.FromBase64String("Aw=="); + + rsa = new RSACryptoServiceProvider(); + rsa.ImportParameters(rsp); + + SortedList dict = CreateDictionaryForLicenseData(xmlDoc); + + //retrieves Signature from SortedList, then removes it + string signature = dict["Signature"].ToString(); + dict.Remove("Signature"); + + IList values = dict.GetValueList(); + + //append values together to form comparable signature + StringBuilder dataString = new StringBuilder(); + foreach(string v in values) + { + dataString.Append(v); + } + + //create byte arrays of both signature and appended values + byte[] signaturebytes = Convert.FromBase64String(signature); + byte[] plainbytes = Encoding.UTF8.GetBytes(dataString.ToString()); + + //then return whether or not license is valid + return rsa.VerifyData(plainbytes, "SHA1", signaturebytes); + } + catch + { + return false; + } + } + + public static SortedList CreateDictionaryForLicenseData(XmlDocument xmlDoc) + { + SortedList result = new SortedList(); + XmlNode xnode = xmlDoc.LastChild.SelectSingleNode("dict"); + + //return if node is null, or does not contain children, or contains an odd amount of children + if(xnode == null || !xnode.HasChildNodes || (xnode.ChildNodes.Count % 2 != 0)) + return result; + + //iterate through the nodes, adding them as key/value pair to SortedList + for (int i = 0; i < xnode.ChildNodes.Count; i++ ) + result.Add(xnode.ChildNodes[i].InnerText, xnode.ChildNodes[++i].InnerText); + + return result; + } + + public static bool isHex(string sHex) + { + Regex r = new Regex(@"^[A-Fa-f0-9]+$"); + return r.IsMatch(sHex); + } + + public static string Hex2B64(string sHex) + { + //removes the 0x from the string if it contains it + sHex = sHex.Replace("0x", string.Empty); + + //tries to determine of the string is Hexidecimal + if (!isHex(sHex)) + return String.Empty; + + //creates a byte array for the Hex + byte[] bytes = new byte[sHex.Length / 2]; + + //iterates through the string, parsing into bytes + int b = 0; + for (int i = 0; i < sHex.Length; i += 2) + { + bytes[b] = byte.Parse(sHex.Substring(i, 2), NumberStyles.HexNumber); + b++; + } + + //returns it as a Base64 string + return Convert.ToBase64String(bytes); + } + + public string PublicKey + { + get { return publicKey; } + set { publicKey = value.Replace("0x", String.Empty); } + } + } +} \ No newline at end of file diff --git a/User/Carbon/AquaticPrime.c b/User/Carbon/AquaticPrime.c new file mode 100644 index 0000000..fd686ce --- /dev/null +++ b/User/Carbon/AquaticPrime.c @@ -0,0 +1,227 @@ +// +// AquaticPrime.c +// AquaticPrime Carbon Implementation +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// -Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// -Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// -Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "AquaticPrime.h" + +static RSA *rsaKey; +static CFStringRef hash; +static CFMutableArrayRef blacklist; + +Boolean APSetKey(CFStringRef key) +{ + hash = CFSTR(""); + blacklist = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + + // Create a new key + rsaKey = RSA_new(); + + // Public exponent is always 3 + BN_hex2bn(&rsaKey->e, "3"); + + CFMutableStringRef mutableKey = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, key); + if (!mutableKey) + return FALSE; + + unsigned int maximumCStringLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(mutableKey), kCFStringEncodingMacRoman) + 1; + char *keyCStringBuffer = malloc(maximumCStringLength); + + // Determine if we have a hex or decimal key + CFStringLowercase(mutableKey, NULL); + if (CFStringHasPrefix(mutableKey, CFSTR("0x"))) { + CFStringTrim(mutableKey, CFSTR("0x")); + CFStringGetCString(mutableKey, keyCStringBuffer, maximumCStringLength, kCFStringEncodingMacRoman); + BN_hex2bn(&rsaKey->n, keyCStringBuffer); + } + else { + CFStringGetCString(mutableKey, keyCStringBuffer, maximumCStringLength, kCFStringEncodingMacRoman); + BN_dec2bn(&rsaKey->n, keyCStringBuffer); + } + CFRelease(mutableKey); + free(keyCStringBuffer); + + return TRUE; +} + +CFStringRef APHash(void) +{ + return CFStringCreateCopy(kCFAllocatorDefault, hash); +} + +void APSetHash(CFStringRef newHash) +{ + if (hash != NULL) + CFRelease(hash); + hash = CFStringCreateCopy(kCFAllocatorDefault, newHash); +} + +// Set the entire blacklist array, removing any existing entries +void APSetBlacklist(CFArrayRef hashArray) +{ + if (blacklist != NULL) + CFRelease(blacklist); + blacklist = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, hashArray); +} + +// Add a single entry to the blacklist-- provided because CFArray doesn't have an equivalent +// for NSArray's +arrayWithObjects, which means it may be easier to pass blacklist entries +// one at a time rather than building an array first and passing the whole thing. +void APBlacklistAdd(CFStringRef blacklistEntry) +{ + CFArrayAppendValue(blacklist, blacklistEntry); +} + +CFDictionaryRef APCreateDictionaryForLicenseData(CFDataRef data) +{ + if (!rsaKey->n || !rsaKey->e) + return NULL; + + // Make the property list from the data + CFStringRef errorString = NULL; + CFPropertyListRef propertyList; + propertyList = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data, kCFPropertyListMutableContainers, &errorString); + if (errorString || CFDictionaryGetTypeID() != CFGetTypeID(propertyList) || !CFPropertyListIsValid(propertyList, kCFPropertyListXMLFormat_v1_0)) { + if (propertyList) + CFRelease(propertyList); + return NULL; + } + + // Load the signature + CFMutableDictionaryRef licenseDictionary = (CFMutableDictionaryRef)propertyList; + if (!CFDictionaryContainsKey(licenseDictionary, CFSTR("Signature"))) { + CFRelease(licenseDictionary); + return NULL; + } + + unsigned char sigBytes[128]; + CFDataRef sigData = CFDictionaryGetValue(licenseDictionary, CFSTR("Signature")); + CFDataGetBytes(sigData, CFRangeMake(0, CFDataGetLength(sigData)), sigBytes); + CFDictionaryRemoveValue(licenseDictionary, CFSTR("Signature")); + + // Decrypt the signature + unsigned char checkDigest[128] = {0}; + if (RSA_public_decrypt(CFDataGetLength(sigData), sigBytes, checkDigest, rsaKey, RSA_PKCS1_PADDING) != SHA_DIGEST_LENGTH) { + CFRelease(licenseDictionary); + return NULL; + } + + // Get the license hash + CFMutableStringRef hashCheck = CFStringCreateMutable(kCFAllocatorDefault,0); + int hashIndex; + for (hashIndex = 0; hashIndex < SHA_DIGEST_LENGTH; hashIndex++) + CFStringAppendFormat(hashCheck, NULL, CFSTR("%02x"), checkDigest[hashIndex]); + APSetHash(hashCheck); + CFRelease(hashCheck); + + if (blacklist && (CFArrayContainsValue(blacklist, CFRangeMake(0, CFArrayGetCount(blacklist)), hash) == true)) + return NULL; + + // Get the number of elements + CFIndex count = CFDictionaryGetCount(licenseDictionary); + // Load the keys and build up the key array + CFMutableArrayRef keyArray = CFArrayCreateMutable(kCFAllocatorDefault, count, NULL); + CFStringRef keys[count]; + CFDictionaryGetKeysAndValues(licenseDictionary, (const void**)&keys, NULL); + int i; + for (i = 0; i < count; i++) + CFArrayAppendValue(keyArray, keys[i]); + + // Sort the array + int context = kCFCompareCaseInsensitive; + CFArraySortValues(keyArray, CFRangeMake(0, count), (CFComparatorFunction)CFStringCompare, &context); + + // Setup up the hash context + SHA_CTX ctx; + SHA1_Init(&ctx); + // Convert into UTF8 strings + for (i = 0; i < count; i++) + { + char *valueBytes; + int valueLengthAsUTF8; + CFStringRef key = CFArrayGetValueAtIndex(keyArray, i); + CFStringRef value = CFDictionaryGetValue(licenseDictionary, key); + + // Account for the null terminator + valueLengthAsUTF8 = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value), kCFStringEncodingUTF8) + 1; + valueBytes = (char *)malloc(valueLengthAsUTF8); + CFStringGetCString(value, valueBytes, valueLengthAsUTF8, kCFStringEncodingUTF8); + SHA1_Update(&ctx, valueBytes, strlen(valueBytes)); + free(valueBytes); + } + unsigned char digest[SHA_DIGEST_LENGTH]; + SHA1_Final(digest, &ctx); + + if (keyArray != NULL) + CFRelease(keyArray); + + // Check if the signature is a match + for (i = 0; i < SHA_DIGEST_LENGTH; i++) { + if (checkDigest[i] ^ digest[i]) { + CFRelease(licenseDictionary); + return NULL; + } + } + + // If it's a match, we return the dictionary; otherwise, we never reach this + return licenseDictionary; +} + +CFDictionaryRef APCreateDictionaryForLicenseFile(CFURLRef path) +{ + // Read the XML file + CFDataRef data; + SInt32 errorCode; + Boolean status; + status = CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, path, &data, NULL, NULL, &errorCode); + + if (errorCode || status != true) + return NULL; + + CFDictionaryRef licenseDictionary = APCreateDictionaryForLicenseData(data); + CFRelease(data); + return licenseDictionary; +} + +Boolean APVerifyLicenseData(CFDataRef data) +{ + CFDictionaryRef licenseDictionary = APCreateDictionaryForLicenseData(data); + if (licenseDictionary) { + CFRelease(licenseDictionary); + return TRUE; + } else { + return FALSE; + } +} + +Boolean APVerifyLicenseFile(CFURLRef path) +{ + CFDictionaryRef licenseDictionary = APCreateDictionaryForLicenseFile(path); + if (licenseDictionary) { + CFRelease(licenseDictionary); + return TRUE; + } else { + return FALSE; + } +} diff --git a/User/Carbon/AquaticPrime.h b/User/Carbon/AquaticPrime.h new file mode 100644 index 0000000..a1c1885 --- /dev/null +++ b/User/Carbon/AquaticPrime.h @@ -0,0 +1,43 @@ +// +// AquaticPrime.h +// AquaticPrime Carbon Implementation +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include + +// Set the key - must be called first +Boolean APSetKey(CFStringRef key); + +// Validating & extracting licenses +CFDictionaryRef APCreateDictionaryForLicenseData(CFDataRef data); +CFDictionaryRef APCreateDictionaryForLicenseFile(CFURLRef path); +Boolean APVerifyLicenseData(CFDataRef data); +Boolean APVerifyLicenseFile(CFURLRef path); + +CFStringRef APHash(); +void APBlacklistAdd(CFStringRef blacklistEntry); +void APSetBlacklist(CFArrayRef hashArray); + diff --git a/User/Cocoa/AquaticPrime.h b/User/Cocoa/AquaticPrime.h new file mode 100644 index 0000000..216836a --- /dev/null +++ b/User/Cocoa/AquaticPrime.h @@ -0,0 +1,74 @@ +// +// AquaticPrime.h +// AquaticPrime Framework +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import +#include +#include +#include + +@interface AquaticPrime : NSObject { + RSA *rsaKey; + + NSString *aqError; + NSString *hash; + NSArray *blacklist; +} + +// Creation ++ (id)aquaticPrimeWithKey:(NSString *)key; ++ (id)aquaticPrimeWithKey:(NSString *)key privateKey:(NSString *)privateKey; +- (id)initWithKey:(NSString *)key; +- (id)initWithKey:(NSString *)key privateKey:(NSString *)privateKey; + +// Getters & Setters +- (BOOL)setKey:(NSString *)key; +- (BOOL)setKey:(NSString *)key privateKey:(NSString *)privateKey; +- (NSString *)key; +- (NSString *)privateKey; +- (void)setHash:(NSString *)newHash; +- (NSString *)hash; + +// Generating license data/files +- (NSData*)licenseDataForDictionary:(NSDictionary *)dict; +- (BOOL)writeLicenseFileForDictionary:(NSDictionary *)dict toPath:(NSString *)path; + +// Validating license data/files +- (NSDictionary *)dictionaryForLicenseData:(NSData *)data; +- (NSDictionary *)dictionaryForLicenseFile:(NSString *)path; +- (BOOL)verifyLicenseData:(NSData *)data; +- (BOOL)verifyLicenseFile:(NSString *)path; + +// Blacklisting +- (void)setBlacklist:(NSArray *)hashArray; + +// Error handling +- (NSString *)getLastError; + +@end + +@interface AquaticPrime (Private) +- (void)_setError:(NSString *)err; +@end diff --git a/User/Cocoa/AquaticPrime.m b/User/Cocoa/AquaticPrime.m new file mode 100644 index 0000000..bf45d4f --- /dev/null +++ b/User/Cocoa/AquaticPrime.m @@ -0,0 +1,356 @@ +// +// AquaticPrime.m +// AquaticPrime Framework +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import "AquaticPrime.h" + +@implementation AquaticPrime + +- (id)init +{ + return [self initWithKey:nil privateKey:nil]; +} + +- (id)initWithKey:(NSString *)key +{ + return [self initWithKey:key privateKey:nil]; +} + +- (id)initWithKey:(NSString *)key privateKey:(NSString *)privateKey +{ + ERR_load_crypto_strings(); + + if (![super init]) + return nil; + + aqError = [[NSString alloc] init]; + blacklist = [[NSArray alloc] init]; + hash = [[NSString alloc] init]; + rsaKey = nil; + + [self setKey:key privateKey:privateKey]; + + return self; +} + +- (void)dealloc +{ + ERR_free_strings(); + + if (rsaKey) + RSA_free(rsaKey); + + [blacklist release]; + [aqError release]; + [hash release]; + + [super dealloc]; +} + ++ (id)aquaticPrimeWithKey:(NSString *)key privateKey:(NSString *)privateKey +{ + return [[[AquaticPrime alloc] initWithKey:key privateKey:privateKey] autorelease]; +} + ++ (id)aquaticPrimeWithKey:(NSString *)key +{ + return [[[AquaticPrime alloc] initWithKey:key privateKey:nil] autorelease]; +} + +- (BOOL)setKey:(NSString *)key +{ + return [self setKey:key privateKey:nil]; +} + +- (BOOL)setKey:(NSString *)key privateKey:(NSString *)privateKey +{ + // Must have public modulus, private key is optional + if (!key || [key isEqualToString:@""]) { + [self _setError:@"Empty public key parameter"]; + return NO; + } + + if (rsaKey) + RSA_free(rsaKey); + + rsaKey = RSA_new(); + + // We are using the constant public exponent e = 3 + BN_dec2bn(&rsaKey->e, "3"); + + // Determine if we have hex or decimal values + int result; + if ([[key lowercaseString] hasPrefix:@"0x"]) + result = BN_hex2bn(&rsaKey->n, (const char *)[[key substringFromIndex:2] UTF8String]); + else + result = BN_dec2bn(&rsaKey->n, (const char *)[key UTF8String]); + + if (!result) { + [self _setError:[NSString stringWithUTF8String:(char*)ERR_error_string(ERR_get_error(), NULL)]]; + return NO; + } + + // Do the private portion if it exists + if (privateKey && ![privateKey isEqualToString:@""]) { + if ([[privateKey lowercaseString] hasPrefix:@"0x"]) + result = BN_hex2bn(&rsaKey->d, (const char *)[[privateKey substringFromIndex:2] UTF8String]); + else + result = BN_dec2bn(&rsaKey->d, (const char *)[privateKey UTF8String]); + + if (!result) { + [self _setError:[NSString stringWithUTF8String:(char*)ERR_error_string(ERR_get_error(), NULL)]]; + return NO; + } + } + + return YES; +} + +- (NSString *)key +{ + if (!rsaKey || !rsaKey->n) + return nil; + + char *cString = BN_bn2hex(rsaKey->n); + + NSString *nString = [[NSString alloc] initWithUTF8String:cString]; + OPENSSL_free(cString); + + return nString; +} + +- (NSString *)privateKey +{ + if (!rsaKey || !rsaKey->d) + return nil; + + char *cString = BN_bn2hex(rsaKey->d); + + NSString *dString = [[NSString alloc] initWithUTF8String:cString]; + OPENSSL_free(cString); + + return dString; +} + +- (void)setHash:(NSString *)newHash +{ + [hash release]; + hash = [newHash retain]; +} + +- (NSString *)hash +{ + return hash; +} + +#pragma mark Blacklisting + +// This array should contain a list of NSStrings representing hexadecimal hashcodes for blacklisted licenses +- (void)setBlacklist:(NSArray*)hashArray +{ + [blacklist release]; + blacklist = [hashArray retain]; +} + +#pragma mark Signing + +- (NSData*)licenseDataForDictionary:(NSDictionary*)dict +{ + // Make sure we have a good key + if (!rsaKey || !rsaKey->n || !rsaKey->d) { + [self _setError:@"RSA key is invalid"]; + return nil; + } + + // Grab all values from the dictionary + NSMutableArray *keyArray = [NSMutableArray arrayWithArray:[dict allKeys]]; + NSMutableData *dictData = [NSMutableData data]; + + // Sort the keys so we always have a uniform order + [keyArray sortUsingSelector:@selector(caseInsensitiveCompare:)]; + + int i; + for (i = 0; i < [keyArray count]; i++) + { + id curValue = [dict objectForKey:[keyArray objectAtIndex:i]]; + char *desc = (char *)[[curValue description] UTF8String]; + // We use strlen instead of [string length] so we can get all the bytes of accented characters + [dictData appendBytes:desc length:strlen(desc)]; + } + + // Hash the data + unsigned char digest[20]; + SHA1([dictData bytes], [dictData length], digest); + + // Create the signature from 20 byte hash + int rsaLength = RSA_size(rsaKey); + unsigned char *signature = (unsigned char*)malloc(rsaLength); + int bytes = RSA_private_encrypt(20, digest, signature, rsaKey, RSA_PKCS1_PADDING); + + if (bytes == -1) { + [self _setError:[NSString stringWithUTF8String:(char*)ERR_error_string(ERR_get_error(), NULL)]]; + return nil; + } + + // Create the license dictionary + NSMutableDictionary *licenseDict = [NSMutableDictionary dictionaryWithDictionary:dict]; + [licenseDict setObject:[NSData dataWithBytes:signature length:bytes] forKey:@"Signature"]; + + // Create the data from the dictionary + NSString *error; + NSData *licenseFile = [[NSPropertyListSerialization dataFromPropertyList:licenseDict + format:kCFPropertyListXMLFormat_v1_0 + errorDescription:&error] retain]; + + if (!licenseFile) { + [self _setError:error]; + return nil; + } + + return licenseFile; +} + +- (BOOL)writeLicenseFileForDictionary:(NSDictionary*)dict toPath:(NSString *)path +{ + NSData *licenseFile = [self licenseDataForDictionary:dict]; + + if (!licenseFile) + return NO; + + return [licenseFile writeToFile:path atomically:YES]; +} + +// This method only logs errors on developer problems, so don't expect to grab an error message if it's just an invalid license +- (NSDictionary*)dictionaryForLicenseData:(NSData *)data +{ + // Make sure public key is set up + if (!rsaKey || !rsaKey->n) { + [self _setError:@"RSA key is invalid"]; + return nil; + } + + // Create a dictionary from the data + NSPropertyListFormat format; + NSString *error; + NSMutableDictionary *licenseDict = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&error]; + if (![licenseDict isKindOfClass:[NSMutableDictionary class]] || error) + return nil; + + NSData *signature = [licenseDict objectForKey:@"Signature"]; + if (!signature) + return nil; + + // Decrypt the signature - should get 20 bytes back + unsigned char checkDigest[20]; + if (RSA_public_decrypt([signature length], [signature bytes], checkDigest, rsaKey, RSA_PKCS1_PADDING) != 20) + return nil; + + // Make sure the license hash isn't on the blacklist + NSMutableString *hashCheck = [NSMutableString string]; + int hashIndex; + for (hashIndex = 0; hashIndex < 20; hashIndex++) + [hashCheck appendFormat:@"%02x", checkDigest[hashIndex]]; + + // Store the license hash in case we need it later + [self setHash:hashCheck]; + + if (blacklist && [blacklist containsObject:hashCheck]) + return nil; + + // Remove the signature element + [licenseDict removeObjectForKey:@"Signature"]; + + // Grab all values from the dictionary + NSMutableArray *keyArray = [NSMutableArray arrayWithArray:[licenseDict allKeys]]; + NSMutableData *dictData = [NSMutableData data]; + + // Sort the keys so we always have a uniform order + [keyArray sortUsingSelector:@selector(caseInsensitiveCompare:)]; + + int objectIndex; + for (objectIndex = 0; objectIndex < [keyArray count]; objectIndex++) + { + id currentValue = [licenseDict objectForKey:[keyArray objectAtIndex:objectIndex]]; + char *description = (char *)[[currentValue description] UTF8String]; + // We use strlen instead of [string length] so we can get all the bytes of accented characters + [dictData appendBytes:description length:strlen(description)]; + } + + // Hash the data + unsigned char digest[20]; + SHA1([dictData bytes], [dictData length], digest); + + // Check if the signature is a match + int checkIndex; + for (checkIndex = 0; checkIndex < 20; checkIndex++) { + if (checkDigest[checkIndex] ^ digest[checkIndex]) + return nil; + } + + return [NSDictionary dictionaryWithDictionary:licenseDict]; +} + +- (NSDictionary*)dictionaryForLicenseFile:(NSString *)path +{ + NSData *licenseFile = [NSData dataWithContentsOfFile:path]; + + if (!licenseFile) + return nil; + + return [self dictionaryForLicenseData:licenseFile]; +} + +- (BOOL)verifyLicenseData:(NSData *)data +{ + if ([self dictionaryForLicenseData:data]) + return YES; + else + return NO; +} + +- (BOOL)verifyLicenseFile:(NSString *)path +{ + NSData *data = [NSData dataWithContentsOfFile:path]; + return [self verifyLicenseData:data]; +} + +#pragma mark Error Handling + +- (NSString*)getLastError +{ + return aqError; +} + +@end + +@implementation AquaticPrime (Private) + +- (void)_setError:(NSString *)err +{ + [aqError release]; + aqError = [err retain]; +} + +@end \ No newline at end of file diff --git a/User/Cocoa/AquaticPrime.xcodeproj/project.pbxproj b/User/Cocoa/AquaticPrime.xcodeproj/project.pbxproj new file mode 100644 index 0000000..db96161 --- /dev/null +++ b/User/Cocoa/AquaticPrime.xcodeproj/project.pbxproj @@ -0,0 +1,494 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 3D12D49D085286600035BA78 /* AquaticPrime.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D12D49B085286600035BA78 /* AquaticPrime.m */; }; + 3D9B64DB086DCB77006322F1 /* AquaticPrime.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D12D49B085286600035BA78 /* AquaticPrime.m */; }; + 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C1666FE841158C02AAC07 /* InfoPlist.strings */; }; + 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 089C1667FE841158C02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; + 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; + 32DBCF5E0370ADEE00C91783 /* AquaticPrime_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AquaticPrime_Prefix.pch; sourceTree = ""; }; + 3D12D49A085286600035BA78 /* AquaticPrime.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AquaticPrime.h; sourceTree = ""; }; + 3D12D49B085286600035BA78 /* AquaticPrime.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AquaticPrime.m; sourceTree = ""; }; + 3D9B64D7086DCB44006322F1 /* libAquaticPrime.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAquaticPrime.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 8DC2EF5A0486A6940098B216 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 8DC2EF5B0486A6940098B216 /* AquaticPrime.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AquaticPrime.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3D9B64D5086DCB44006322F1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DC2EF560486A6940098B216 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DC2EF570486A6940098B216 /* Cocoa.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + 8DC2EF5B0486A6940098B216 /* AquaticPrime.framework */, + 3D9B64D7086DCB44006322F1 /* libAquaticPrime.a */, + ); + name = Products; + sourceTree = ""; + }; + 0867D691FE84028FC02AAC07 /* AquaticPrime */ = { + isa = PBXGroup; + children = ( + 08FB77AEFE84172EC02AAC07 /* Classes */, + 32C88DFF0371C24200C91783 /* Other Sources */, + 089C1665FE841158C02AAC07 /* Resources */, + 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */, + 034768DFFF38A50411DB9C8B /* Products */, + ); + name = AquaticPrime; + sourceTree = ""; + }; + 0867D69AFE84028FC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + 1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */, + ); + name = "External Frameworks and Libraries"; + sourceTree = ""; + }; + 089C1665FE841158C02AAC07 /* Resources */ = { + isa = PBXGroup; + children = ( + 8DC2EF5A0486A6940098B216 /* Info.plist */, + 089C1666FE841158C02AAC07 /* InfoPlist.strings */, + ); + name = Resources; + sourceTree = ""; + }; + 08FB77AEFE84172EC02AAC07 /* Classes */ = { + isa = PBXGroup; + children = ( + 3D12D49A085286600035BA78 /* AquaticPrime.h */, + 3D12D49B085286600035BA78 /* AquaticPrime.m */, + ); + name = Classes; + sourceTree = ""; + }; + 1058C7B0FEA5585E11CA2CBB /* Linked Frameworks */ = { + isa = PBXGroup; + children = ( + 1058C7B1FEA5585E11CA2CBB /* Cocoa.framework */, + ); + name = "Linked Frameworks"; + sourceTree = ""; + }; + 32C88DFF0371C24200C91783 /* Other Sources */ = { + isa = PBXGroup; + children = ( + 32DBCF5E0370ADEE00C91783 /* AquaticPrime_Prefix.pch */, + ); + name = "Other Sources"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 3D9B64D6086DCB44006322F1 /* libAquaticPrime */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3D9B64DC086DCB83006322F1 /* Build configuration list for PBXNativeTarget "libAquaticPrime" */; + buildPhases = ( + 3D9B64D4086DCB44006322F1 /* Sources */, + 3D9B64D5086DCB44006322F1 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = libAquaticPrime; + productName = libAquaticPrime; + productReference = 3D9B64D7086DCB44006322F1 /* libAquaticPrime.a */; + productType = "com.apple.product-type.library.static"; + }; + 8DC2EF4F0486A6940098B216 /* AquaticPrime */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3D9364620856794A006F1864 /* Build configuration list for PBXNativeTarget "AquaticPrime" */; + buildPhases = ( + 8DC2EF520486A6940098B216 /* Resources */, + 8DC2EF540486A6940098B216 /* Sources */, + 8DC2EF560486A6940098B216 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = AquaticPrime; + productInstallPath = "$(HOME)/Library/Frameworks"; + productName = AquaticPrime; + productReference = 8DC2EF5B0486A6940098B216 /* AquaticPrime.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 3D9364660856794A006F1864 /* Build configuration list for PBXProject "AquaticPrime" */; + compatibilityVersion = "Xcode 2.4"; + hasScannedForEncodings = 1; + mainGroup = 0867D691FE84028FC02AAC07 /* AquaticPrime */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DC2EF4F0486A6940098B216 /* AquaticPrime */, + 3D9B64D6086DCB44006322F1 /* libAquaticPrime */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8DC2EF520486A6940098B216 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DC2EF530486A6940098B216 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3D9B64D4086DCB44006322F1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3D9B64DB086DCB77006322F1 /* AquaticPrime.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8DC2EF540486A6940098B216 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3D12D49D085286600035BA78 /* AquaticPrime.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 089C1666FE841158C02AAC07 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 089C1667FE841158C02AAC07 /* English */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 3D9364630856794A006F1864 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = NO; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AquaticPrime_Prefix.pch; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + LIBRARY_STYLE = DYNAMIC; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.3; + MACOSX_DEPLOYMENT_TARGET_i386 = 10.4; + MACOSX_DEPLOYMENT_TARGET_ppc = 10.3; + OTHER_LDFLAGS = ( + "-lcrypto", + "-seg1addr", + 0x10000000, + ); + PREBINDING = YES; + PRODUCT_NAME = AquaticPrime; + WRAPPER_EXTENSION = framework; + ZERO_LINK = YES; + }; + name = Development; + }; + 3D9364640856794A006F1864 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AquaticPrime_Prefix.pch; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + LIBRARY_STYLE = DYNAMIC; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.3; + MACOSX_DEPLOYMENT_TARGET_i386 = 10.4; + MACOSX_DEPLOYMENT_TARGET_ppc = 10.3; + OTHER_LDFLAGS = ( + "-lcrypto", + "-seg1addr", + 0x10000000, + ); + PREBINDING = YES; + PRODUCT_NAME = AquaticPrime; + WRAPPER_EXTENSION = framework; + ZERO_LINK = NO; + }; + name = Deployment; + }; + 3D9364650856794A006F1864 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + FRAMEWORK_VERSION = A; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = AquaticPrime_Prefix.pch; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "@executable_path/../Frameworks"; + LIBRARY_STYLE = DYNAMIC; + MACH_O_TYPE = mh_dylib; + MACOSX_DEPLOYMENT_TARGET = 10.3; + MACOSX_DEPLOYMENT_TARGET_i386 = 10.4; + MACOSX_DEPLOYMENT_TARGET_ppc = 10.3; + OTHER_LDFLAGS = ( + "-lcrypto", + "-seg1addr", + 0x10000000, + ); + PREBINDING = YES; + PRODUCT_NAME = AquaticPrime; + WRAPPER_EXTENSION = framework; + }; + name = Default; + }; + 3D9364670856794A006F1864 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Development; + }; + 3D9364680856794A006F1864 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Deployment; + }; + 3D9364690856794A006F1864 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + MACOSX_DEPLOYMENT_TARGET = 10.4; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Default; + }; + 3D9B64DD086DCB83006322F1 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; + INSTALL_PATH = /usr/local/lib; + LIBRARY_STYLE = STATIC; + MACOSX_DEPLOYMENT_TARGET_i386 = 10.4; + MACOSX_DEPLOYMENT_TARGET_ppc = 10.3; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + "-framework", + AppKit, + ); + PREBINDING = NO; + PRODUCT_NAME = AquaticPrime; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + name = Development; + }; + 3D9B64DE086DCB83006322F1 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; + INSTALL_PATH = /usr/local/lib; + LIBRARY_STYLE = STATIC; + MACOSX_DEPLOYMENT_TARGET_i386 = 10.4; + MACOSX_DEPLOYMENT_TARGET_ppc = 10.3; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + "-framework", + AppKit, + ); + PREBINDING = NO; + PRODUCT_NAME = AquaticPrime; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + name = Deployment; + }; + 3D9B64DF086DCB83006322F1 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h"; + INSTALL_PATH = /usr/local/lib; + LIBRARY_STYLE = STATIC; + MACOSX_DEPLOYMENT_TARGET_i386 = 10.4; + MACOSX_DEPLOYMENT_TARGET_ppc = 10.3; + OPTIMIZATION_CFLAGS = "-O0"; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + "-framework", + AppKit, + ); + PREBINDING = NO; + PRODUCT_NAME = AquaticPrime; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + name = Default; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3D9364620856794A006F1864 /* Build configuration list for PBXNativeTarget "AquaticPrime" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3D9364630856794A006F1864 /* Development */, + 3D9364640856794A006F1864 /* Deployment */, + 3D9364650856794A006F1864 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + 3D9364660856794A006F1864 /* Build configuration list for PBXProject "AquaticPrime" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3D9364670856794A006F1864 /* Development */, + 3D9364680856794A006F1864 /* Deployment */, + 3D9364690856794A006F1864 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; + 3D9B64DC086DCB83006322F1 /* Build configuration list for PBXNativeTarget "libAquaticPrime" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3D9B64DD086DCB83006322F1 /* Development */, + 3D9B64DE086DCB83006322F1 /* Deployment */, + 3D9B64DF086DCB83006322F1 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/User/Cocoa/AquaticPrime_Prefix.pch b/User/Cocoa/AquaticPrime_Prefix.pch new file mode 100644 index 0000000..362dc5a --- /dev/null +++ b/User/Cocoa/AquaticPrime_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'AquaticPrime' target in the 'AquaticPrime' project. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/User/Cocoa/English.lproj/InfoPlist.strings b/User/Cocoa/English.lproj/InfoPlist.strings new file mode 100644 index 0000000..2c04f47 Binary files /dev/null and b/User/Cocoa/English.lproj/InfoPlist.strings differ diff --git a/User/Cocoa/Info.plist b/User/Cocoa/Info.plist new file mode 100644 index 0000000..2045a35 --- /dev/null +++ b/User/Cocoa/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + AquaticPrime + CFBundleIconFile + + CFBundleIdentifier + com.aquaticmac.AquaticPrime + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + FMWK + CFBundleSignature + ???? + CFBundleVersion + 1.0.2 + NSPrincipalClass + AquaticPrime + + diff --git a/User/PHP/AquaticPrime.php b/User/PHP/AquaticPrime.php new file mode 100644 index 0000000..f71bd6b --- /dev/null +++ b/User/PHP/AquaticPrime.php @@ -0,0 +1,229 @@ + '0', '1' => '1', '2' => '2', + '3' => '3', '4' => '4', '5' => '5', + '6' => '6', '7' => '7', '8' => '8', + '9' => '9', 'A' => '10', 'B' => '11', + 'C' => '12', 'D' => '13', 'E' => '14', + 'F' => '15', 'a' => '10', 'b' => '11', + 'c' => '12', 'd' => '13', 'e' => '14', + 'f' => '15'); + $decval = '0'; + + $number = array_pop(explode("0x", $number, 2)); + + $number = strrev($number); + for($i = 0; $i < strlen($number); $i++) + { + $decval = bcadd(bcmul(bcpow('16',$i,0),$decvalues[$number{$i}]), $decval); + } + return $decval; +} + +/** + * powmod + * Raise a number to a power mod n + * This could probably be made faster with some Montgomery trickery, but it's just fallback for now + * @param string Decimal string to be raised + * @param string Decimal string of the power to raise to + * @param string Decimal string the modulus + * @return string Decimal string + */ +function powmod($num, $pow, $mod) +{ + if (function_exists('bcpowmod')) { + // bcpowmod is only available under PHP5 + return bcpowmod($num, $pow, $mod); + } + + // emulate bcpowmod + $result = '1'; + do { + if (!bccomp(bcmod($pow, '2'), '1')) { + $result = bcmod(bcmul($result, $num), $mod); + } + $num = bcmod(bcpow($num, '2'), $mod); + $pow = bcdiv($pow, '2'); + } while (bccomp($pow, '0')); + return $result; +} + +/** + * getSignature + * Get the base64 signature of a dictionary + * @param array Associative array (i.e. dictionary) of key-value pairs + * @param string Hexadecimal string of public key + * @param string Hexadecimal string the private key + * @return string Base64 encoded signature + */ +function getSignature($dict, $key, $privKey) +{ + // Sort keys alphabetically + uksort($dict, "strcasecmp"); + + // Concatenate all values + $total = ''; + foreach ($dict as $value) + $total .= $value; + + // Escape apostrophes by un-quoting, adding apos, then re-quoting + // so this turns ' into '\'' ... we have to double-slash for this php. + $fixedApostrophes = escapeshellarg($total); + + // This part is the most expensive below + // We try to do it with native code first + $aquatic_root = preg_replace('!((/[A-Za-z._-]+)+)/AquaticPrime\.php!', '$1', __FILE__); + ob_start(); + $passthruString = $aquatic_root."/aquaticprime $key $privKey '$fixedApostrophes'"; + + passthru($passthruString, $err); + $sig = ob_get_contents(); + ob_end_clean(); + if ($err) + { + error_log("passthrough yielded $err: $passthruString"); + } + + // If that fails, do it in php + if ($sig != "") + { + $sig = base64_encode($sig); + } + else + { + // Get the hash + $hash = sha1(utf8_encode($total)); + + // OpenSSL-compatible PKCS1 Padding + // 128 bytes - 20 bytes hash - 3 bytes extra padding = 105 bytes '0xff' + $paddedHash = '0001'; + for ($i = 0; $i < 105; $i++) + { + $paddedHash .= 'ff'; + } + $paddedHash .= '00'.$hash; + + $decryptedSig = hex2dec($paddedHash); + + // Encrypt into a signature + $sig = powmod($decryptedSig, hex2dec($privKey), hex2dec($key)); + $sig = base64_encode(hex2bin(dec2hex($sig))); + } + return $sig; +} + +/** + * licenseDataForDictionary + * Get the signed plist for a dictionary + * @param array Associative array (i.e. dictionary) of key-value pairs + * @param string Hexadecimal string of public key + * @param string Hexadecimal string the private key + * @return string License file as plist + */ +function licenseDataForDictionary($dict, $pubKey, $privKey) +{ + $sig = chunk_split(getSignature($dict, $pubKey, $privKey)); + + $plist = "\n"; + $plist .= "\n"; + $plist .= "\n\n"; + + foreach ($dict as $key => $value) { + $plist .= "\t".htmlspecialchars($key, ENT_NOQUOTES)."\n"; + $plist .= "\t".htmlspecialchars($value, ENT_NOQUOTES)."\n"; + } + + $plist .= "\tSignature\n"; + $plist .= "\t$sig\n"; + $plist .= "\n"; + $plist .= "\n"; + + return $plist; +} + +function sendMail($to, $from, $subject, $message, $license, $name, $bcc='') +{ + // Create a random boundary + $boundary = base64_encode(MD5((string)rand())); + + $headers = "From: $from\n"; + if ($bcc != "") + $headers .= "Bcc: $bcc\n"; + $headers .= "X-Mailer: PHP/".phpversion()."\n"; + $headers .= "MIME-Version: 1.0\n"; + $headers .= "Content-Type: multipart/mixed; boundary=\"$boundary\"\n"; + $headers .= "Content-Transfer-Encoding: 8bit\n\n"; + $headers .= "This is a MIME encoded message.\n\n"; + + $headers .= "--$boundary\n"; + + $headers .= "Content-Type: text/plain; charset=\"utf-8\"\n"; + $headers .= "Content-Transfer-Encoding: 8bit\n\n"; + $headers .= "$message\n\n\n"; + + $headers .= "--$boundary\n"; + + $headers .= "Content-Type: application/octet-stream; name=\"$name\"\n"; + $headers .= "Content-Transfer-Encoding: base64\n"; + $headers .= "Content-Disposition: attachment\n\n"; + + $headers .= chunk_split(base64_encode($license))."\n"; + + $headers .= "--$boundary--"; + + mail($to, $subject, "", utf8_encode($headers)); +} + +?> \ No newline at end of file diff --git a/User/PHP/AquaticPrimeBMTMicro.php b/User/PHP/AquaticPrimeBMTMicro.php new file mode 100644 index 0000000..cff0c8b --- /dev/null +++ b/User/PHP/AquaticPrimeBMTMicro.php @@ -0,0 +1,122 @@ +tag_name = $name; + } + + function endElement($parser, $name) + { + $this->tag_name = NULL; + } + + function characterData($parser, $data) + { + if($this->tag_name != NULL) + { + $this->tag_data[$this->tag_name] = $data; + } + } + + function parse($data) + { + $xml_parser = xml_parser_create(); + xml_set_object($xml_parser, $this); + xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false); + xml_set_element_handler($xml_parser, "startElement", "endElement"); + xml_set_character_data_handler($xml_parser, "characterData"); + $success = xml_parse($xml_parser, $data, true); + + if(!$success) + { + $this->tag_data['error'] = sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($xml_parser)), xml_get_current_line_number($xml_parser)); + } + + xml_parser_free($xml_parser); + + return($success); + } + + function getElement($tag) + { + return($this->tag_data[$tag]); + } +} + +// parse the XML data +$bmtparser = new BMTXMLParser(); + +// Work around a PHP 5.2.2 bug preventing POST data from reaching the script. +// See for details. +if ($_SERVER["REQUEST_METHOD"] == "POST") { + if ( !isset( $HTTP_RAW_POST_DATA ) ) { + $HTTP_RAW_POST_DATA = file_get_contents("php://input"); + } +} + +if($bmtparser->parse($HTTP_RAW_POST_DATA)) +{ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; +} +else +{ + echo ''; + echo ''; + echo ''; + echo '1'; + echo '' . $bmtparser->getElement('error') . ''; + echo ''; + echo ''; + + die(); +} + +// generate the serial + +// Create our license dictionary to be signed +$dict = array("Product" => $bmtparser->getElement('productname'), + "Name" => $bmtparser->getElement('registername'), + "Email" => $bmtparser->getElement('email'), + "OrderID" => $bmtparser->getElement('orderid')); + +$license = licenseDataForDictionary($dict, $key, $privateKey); + +// send the e-mail with that serial + +$name = $bmtparser->getElement('registername'); +$email = $bmtparser->getElement('email'); + +$to = $email; + +$from = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $from); +$subject = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $subject); +$message = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $message); +$licenseName = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $licenseName); +$bcc = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $bcc); + +sendMail($to, $from, $subject, $message, $license, $licenseName, $bcc); + +?> diff --git a/User/PHP/AquaticPrimeCLI.c b/User/PHP/AquaticPrimeCLI.c new file mode 100644 index 0000000..1b4dd03 --- /dev/null +++ b/User/PHP/AquaticPrimeCLI.c @@ -0,0 +1,59 @@ +// +// AquaticPrimeCLI.m +// AquaticPrime +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + RSA* rsaKey; + unsigned char digest[20], signature[128]; + int i, encryptedLength; + + if (argc != 4) + return -1; + + rsaKey = RSA_new(); + BN_hex2bn(&rsaKey->n, argv[1]); + BN_hex2bn(&rsaKey->d, argv[2]); + BN_dec2bn(&rsaKey->e, "3"); + + if (BN_num_bits(rsaKey->n) != 1024) { + RSA_free(rsaKey); + return -1; + } + + SHA1((unsigned char*)argv[3], strlen(argv[3]), digest); + + encryptedLength = RSA_private_encrypt(20, digest, signature, rsaKey, RSA_PKCS1_PADDING); + + for (i = 0; i < encryptedLength; i++) + fprintf(stdout, "%c", signature[i]); + + return 0; +} diff --git a/User/PHP/AquaticPrimeEsellerate.php b/User/PHP/AquaticPrimeEsellerate.php new file mode 100644 index 0000000..c9f909e --- /dev/null +++ b/User/PHP/AquaticPrimeEsellerate.php @@ -0,0 +1,102 @@ + for details. + if ($_SERVER["REQUEST_METHOD"] == "POST") { + if ( !isset( $HTTP_RAW_POST_DATA ) ) { + $HTTP_RAW_POST_DATA = file_get_contents("php://input"); + } + } + + $xml_parser = xml_parser_create(); + xml_set_element_handler($xml_parser, "startElement", "endElement"); + xml_set_character_data_handler($xml_parser, "characterData"); + + if (!xml_parse($xml_parser, $HTTP_RAW_POST_DATA, TRUE)) + { + $msg = sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($xml_parser)), + xml_get_current_line_number($xml_parser)); + xml_parser_free($xml_parser); + ReportFatalError($msg); + } + + xml_parser_free($xml_parser); + + // We have all the data in the $XmlData array + + // Check the secret text + if ($XmlData["ORDERNOTICEDS"]["ORDERINFO"]["ORDER_NOTICE_SECRET"]["_data"] != $order_notice_secret) { + ReportFatalError("Invalid order notice secret\n"); + } + + // Ignore Preview orders + if (($debug == 0) && ($XmlData["ORDERNOTICEDS"]["ORDERINFO"]["STATUS"]["_data"] == "PREVIEW")) + { + ReportFatalError("No processing of preview order except in debug mode.\n"); + } + + // For Aquatic Prime we really need the date and time (in case someone orders more than one copy + // the same day), so the date from eSellerate won't cut it. + $sn_date = strftime("%Y-%m-%d %H:%m:%S"); + // Process each order line + for ($i = 0; $i < $nOrderLines; ++$i) + { + // Now do the AquaticPrime stuff + if (in_array($XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["SKU_ID"]["_data"], $aquaticPrimeSKUs)) { + $product = $XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["SKU_TITLE"]["_data"]; + $name = $XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["REGISTRATION_NAME"]["_data"]; + $email = $XmlData["ORDERNOTICEDS"]["ORDERINFO"]["EMAIL"]["_data"]; + $unit_price = $XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["UNIT_PRICE"]["_data"]; + $count = $XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["QUANTITY"]["_data"]; + // eSellerate only gives you the date, not the time (so we don't do RFC 2822 formatting here). + $transactionID = $orderNumber; + // Create our license dictionary to be signed + $dict = array("Product" => $product, + "Name" => $name, + "Email" => $email, + "Licenses" => $count, + "Timestamp" => $sn_date, + "TransactionID" => $transactionID); + $license = licenseDataForDictionary($dict, $key, $privateKey); + + $to = $email; + + $from = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $from); + $subject = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $subject); + $message = str_replace(array("##NAME##", "##EMAIL##", "##LICENSES##"), array($name, $email, $count), $message); + $licenseName = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $licenseName); + $bcc = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $bcc); + + sendMail($to, $from, $subject, $message, $license, $licenseName, $bcc); + } + + } + +?> diff --git a/User/PHP/AquaticPrimeEsellerateMySQL.php b/User/PHP/AquaticPrimeEsellerateMySQL.php new file mode 100644 index 0000000..b67f624 --- /dev/null +++ b/User/PHP/AquaticPrimeEsellerateMySQL.php @@ -0,0 +1,250 @@ + for details. + if ($_SERVER["REQUEST_METHOD"] == "POST") { + if ( !isset( $HTTP_RAW_POST_DATA ) ) { + $HTTP_RAW_POST_DATA = file_get_contents("php://input"); + } + } + + $xml_parser = xml_parser_create(); + xml_set_element_handler($xml_parser, "startElement", "endElement"); + xml_set_character_data_handler($xml_parser, "characterData"); + + if (!xml_parse($xml_parser, $HTTP_RAW_POST_DATA, TRUE)) + { + $msg = sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($xml_parser)), + xml_get_current_line_number($xml_parser)); + xml_parser_free($xml_parser); + ReportFatalError($msg); + } + + xml_parser_free($xml_parser); + + // We have all the data in the $XmlData array + + // Check the secret text + if ($XmlData["ORDERNOTICEDS"]["ORDERINFO"]["ORDER_NOTICE_SECRET"]["_data"] != $order_notice_secret) { + ReportFatalError("Invalid order notice secret\n"); + } + + // Ignore Preview orders + if (($debug == 0) && ($XmlData["ORDERNOTICEDS"]["ORDERINFO"]["STATUS"]["_data"] == "PREVIEW")) + { + ReportFatalError("No processing of preview order except in debug mode.\n"); + } + + // We will open a MySql database and store the serial numbers + if (!TryOpenDb()) + { + ReportFatalError($DbError); + } + + // The tables in eSellerate.sql are type InnoDB, so we can have the safety of transactions. + // The corresponding COMMIT is at the end of this file. The ROLLBACK, if necessary, is over in ReportFatalError(). + mysql_query("BEGIN"); + + //$date = date("Y/m/d"); + // Fix up the transaction date to give MySQL something it likes. + $tran_date_str = $XmlData["ORDERNOTICEDS"]["ORDERINFO"]["TRAN_DATE"]["_data"]; + $tran_date_stamp = strtotime($tran_date_str); + $tran_date_for_sql = strftime("%Y-%m-%d", $tran_date_stamp); + $XmlData["ORDERNOTICEDS"]["ORDERINFO"]["TRAN_DATE"]["_data"] = $tran_date_for_sql; + + // Write the OrderInfo stuff to the database + $orderNumber = $XmlData["ORDERNOTICEDS"]["ORDERINFO"]["ORDER_NUMBER"]["_data"]; + // First check and see whether the order's already in the database. + $sqlResult = mysql_query("SELECT ORDER_NUMBER from OrderInfo WHERE ORDER_NUMBER=\"$orderNumber\""); + if (!$sqlResult) { + ReportFatalError(mysql_error()); + } + $numRows = mysql_num_rows($sqlResult); + mysql_free_result($sqlResult); + if ($numRows > 0) { + ReportFatalError("Order $orderNumber is already in the database\n"); + } + // It wasn't there? OK, put it there. + // Build a query string + $queryStringValues = array(); + foreach ($orderInfoFields as $currentField) { + $queryStringValues[] = "\"" . mysql_real_escape_string($XmlData["ORDERNOTICEDS"]["ORDERINFO"][$currentField]["_data"]) . "\""; + } + $queryString = "INSERT INTO OrderInfo (" . join(", ", $orderInfoFields) . ")" . + " VALUES (" . join(", ", $queryStringValues) . ")"; + if ($debug == 1) { + echo "$queryString\n"; + } + // Do the insert + $sqlResult = mysql_query($queryString); + if (!$sqlResult) { + ReportFatalError(mysql_error()); + } + + // For Aquatic Prime we really need the date and time (in case someone orders more than one copy + // the same day), so the date from eSellerate won't cut it. + $sn_date = strftime("%Y-%m-%d %H:%m:%S"); + // Process each order line + for ($i = 0; $i < $nOrderLines; ++$i) + { + // Now do the AquaticPrime stuff + if (in_array($XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["SKU_ID"]["_data"], $aquaticPrimeSKUs)) { + $product = $XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["SKU_TITLE"]["_data"]; + $name = $XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["REGISTRATION_NAME"]["_data"]; + if ($name == "") { + $name = $XmlData["ORDERNOTICEDS"]["ORDERINFO"]["FIRST_NAME"]["_data"] . " " . + $XmlData["ORDERNOTICEDS"]["ORDERINFO"]["LAST_NAME"]["_data"]; + } + $email = $XmlData["ORDERNOTICEDS"]["ORDERINFO"]["EMAIL"]["_data"]; + $unit_price = $XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["UNIT_PRICE"]["_data"]; + $count = $XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["QUANTITY"]["_data"]; + // eSellerate only gives you the date, not the time (so we don't do RFC 2822 formatting here). + $transactionID = $orderNumber; + // Create our license dictionary to be signed + $dict = array("Product" => $product, + "Name" => $name, + "Email" => $email, + "Licenses" => $count, + "Timestamp" => $sn_date, + "TransactionID" => $transactionID); + $license = licenseDataForDictionary($dict, $key, $privateKey); + + // Note that the database size for SERIAL_NUMBER was raised from 255 (eSellerate's size) to + // a MySQL TEXT field to fit alternate registration schemes. + $XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i]["SERIAL_NUMBER"]["_data"] = $license; + + $to = $email; + + $from = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $from); + $subject = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $subject); + $message = str_replace(array("##NAME##", "##EMAIL##", "##LICENSES##"), array($name, $email, $count), $message); + $licenseName = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $licenseName); + $bcc = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $bcc); + + sendMail($to, $from, $subject, $message, $license, $licenseName, $bcc); + } + + // Build a query string + $queryStringValues = array(); + foreach ($orderLinesFields as $currentField) { + $queryStringValues[] = "\"" . mysql_real_escape_string($XmlData["ORDERNOTICEDS"]["ORDERLINES"][$i][$currentField]["_data"]) . "\""; + } + $queryString = "INSERT INTO OrderLines (" . join(", ", $orderLinesFields) . ", ORDER_NUMBER, SN_DATE)" . + " VALUES (" . join(", ", $queryStringValues) . ", \"$orderNumber\", \"$sn_date\")"; + if ($debug == 1) { + echo "$queryString\n"; + } + // Do the insert + + $sqlResult = mysql_query($queryString); + if (!$sqlResult) { + ReportFatalError(mysql_error()); + } + + } + + mysql_query("COMMIT"); + CloseDb(); + +?> diff --git a/User/PHP/AquaticPrimeKagi.php b/User/PHP/AquaticPrimeKagi.php new file mode 100644 index 0000000..854e1ea --- /dev/null +++ b/User/PHP/AquaticPrimeKagi.php @@ -0,0 +1,73 @@ + $product, + "Name" => $name, + "Email" => $email, + "Licenses" => $count, + "Timestamp" => $timestamp, + "TransactionID" => $transactionID); + +$license = licenseDataForDictionary($dict, $key, $privateKey); + +$to = $email; + +// Handle test orders by setting the To: email to the BCC: email +if (stristr(urldecode($_POST["ACG:Flags"]), "test=1")) + $to = $bcc; + +$from = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $from); +$subject = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $subject); +$message = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $message); +$licenseName = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $licenseName); +$bcc = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $bcc); + +sendMail($to, $from, $subject, $message, $license, $licenseName, $bcc); + +header("Content-type: text/text"); +echo "kagiRemotePostStatus=GOOD, message=Transaction completed.\r\n\r\n"; + +?> \ No newline at end of file diff --git a/User/PHP/AquaticPrimePayPal.php b/User/PHP/AquaticPrimePayPal.php new file mode 100644 index 0000000..41e5c11 --- /dev/null +++ b/User/PHP/AquaticPrimePayPal.php @@ -0,0 +1,98 @@ + $product, + "Name" => $name, + "Email" => $email, + "Licenses" => $count, + "Timestamp" => $timestamp, + "TransactionID" => $transactionID); + +$license = licenseDataForDictionary($dict, $key, $privateKey); + +$to = $email; + +$from = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $from); +$subject = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $subject); +$message = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $message); +$licenseName = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $licenseName); +$bcc = str_replace(array("##NAME##", "##EMAIL##"), array($name, $email), $bcc); + +sendMail($to, $from, $subject, $message, $license, $licenseName, $bcc); + +fclose ($fp); + +header("Location: $redirect_url"); +?> diff --git a/User/PHP/Config.php b/User/PHP/Config.php new file mode 100644 index 0000000..d6ab9ce --- /dev/null +++ b/User/PHP/Config.php @@ -0,0 +1,72 @@ + + +---YOUR INSTALL INSTRUCTIONS--- to register $product. + +Thanks, +---YOUR NAME HERE---"; + +// It's a good idea to BCC your own email here so you can have an order history +$bcc = "orders@$domain"; + +// This is the name of the license file that will be attached to the email +$licenseName = "##NAME##.---YOUR LICENSE EXTENSION---"; + +// ---KAGI ONLY CONFIG---- + +$kagiPassword = "testPassword"; + + +// ---PAYPAL ONLY CONFIG---- + +// Your PDT authorization token +$auth_token = "AUTH TOKEN HERE"; +// Put in a URL here to redirect back to after the transaction +$redirect_url = "http://$domain/thanks.html"; +$error_url = "http://$domain/error.html"; + + +// ---ESELLERATE ONLY CONFIG---- +// Secret text set up in your eSellerate publisher account +$order_notice_secret = "my secret esellerate string"; +// List of eSellerate SKUs that should be processed by AquaticPrime. Included because things like +// eCDs will come through as a separate SKU, but you probably don't want to run the order through +// AquaticPrime. Anything not in this list will be ignored. +$aquaticPrimeSKUs = array( + "SKU1234567890" + ); + + +// ---MYSQL CONFIG---- + +// Database of registrations +$db_host = "--DATABASE HOST HERE --"; +$db_user = "--DATABASE USER HERE--"; +$db_password = "--DATABASE PW HERE--"; +$db_name = "--DATABASE NAME HERE--"; + +?> diff --git a/User/PHP/KagiTest.php b/User/PHP/KagiTest.php new file mode 100644 index 0000000..c858a18 --- /dev/null +++ b/User/PHP/KagiTest.php @@ -0,0 +1,44 @@ + + + + AquaticPrime Kagi Test + + + +
+

+ Password:
+ Product Name:
+ Purchaser Name:
+ Purchaser Email:
+ Quantity:
+ Date:
+ + + + + + +

+
+ + \ No newline at end of file diff --git a/User/PHP/Makefile b/User/PHP/Makefile new file mode 100644 index 0000000..217b829 --- /dev/null +++ b/User/PHP/Makefile @@ -0,0 +1,14 @@ +CC = gcc +CFLAGS = -g -O2 -I/usr/local/include -I/usr/local/lib +LFLAGS = -lcrypto + +all: aquaticprime + +aquaticprime: ${OBJS} + ${CC} ${CFLAGS} AquaticPrimeCLI.c ${LFLAGS} -o aquaticprime + +install: aquaticprime + cp aquaticprime /usr/local/bin/ + +clean: + rm aquaticprime \ No newline at end of file diff --git a/User/PHP/eSellerate.sql b/User/PHP/eSellerate.sql new file mode 100644 index 0000000..f89e3e6 --- /dev/null +++ b/User/PHP/eSellerate.sql @@ -0,0 +1,146 @@ +-- Sample tables that can be used to store everything received in an +-- eSellerate XML order notice. Except where noted, field types and sizes are +-- based on "OrderNoticeDS.xsd" and "OrderNoticeDS.xsd Reference.pdf". +-- The tables were designed to target MySQL 4.1.18, and may or may not need +-- customizing for other database engines. + +-- These tables include everything from the XML post (except where noted). +-- However all fields except ORDER_NUMBER (in OrderInfo) and SKU_ID (in OrderLines) +-- are allowed to be NULL, so you can choose to just not populate fields you're not interested in. +-- In particular if you just want to set up a password-retreival database, you might +-- want to leave out everything except serial number and email address. + +-- Tom Harrington, tph@atomicbird.com, May 5 2006 + +-- (details on MySQL foreign key syntax: http://dev.mysql.com/doc/refman/4.1/en/innodb-foreign-key-constraints.html) + +-- $Id: eSellerate.sql 210 2007-09-06 23:19:09Z atomicbird $ +DROP TABLE IF EXISTS OrderLines; +DROP TABLE IF EXISTS OrderInfo; +DROP TABLE IF EXISTS SNLookupHistoryEntry; + +-- OrderInfo has general information about the order and the customer +-- Note the following fields from the XML post are intentionally left out as not being useful here: +-- ORDER_NOTICE_SECRET +-- ORDER_NOTICE_URL +-- PUBLISHER_ID +-- PUBLISHER +CREATE TABLE OrderInfo ( + CUSTOMER_IP CHAR(50), + ORDER_NUMBER CHAR(50) NOT NULL, + PRIMARY KEY (ORDER_NUMBER), + STATUS CHAR(50), + TRAN_DATE DATE, -- eSellerate defines this as a string up to 50 chars long. + FIRST_NAME CHAR(50), + LAST_NAME CHAR(50), + COMPANY CHAR(50), + ADDRESS1 CHAR(100), + ADDRESS2 CHAR(100), + CITY CHAR(50), + STATE CHAR(50), + POSTAL CHAR(50), + COUNTRY CHAR(50), + PHONE CHAR(50), + EMAIL CHAR(50), + SHIP_FIRST_NAME CHAR(50), + SHIP_LAST_NAME CHAR(50), + SHIP_COMPANY CHAR(50), + SHIP_ADDRESS1 CHAR(100), + SHIP_ADDRESS2 CHAR(100), + SHIP_CITY CHAR(50), + SHIP_STATE CHAR(50), + SHIP_POSTAL CHAR(50), + SHIP_COUNTRY CHAR(50), + CONTACT_ME TINYINT, + ORDER_DISCOUNT DECIMAL(10,2), + SHIP_WEIGHT DECIMAL(10,2), + SHIP_METHOD CHAR(50), + SHIP_AMOUNT DECIMAL(10,2), + ESELLER_ID CHAR(50), + ESELLER_NAME CHAR(50), + METHOD CHAR(50), + SUB_METHOD CHAR(50), + COUPON_ID CHAR(100), + TRACKING_ID TEXT, -- Changed to TEXT because eSellerate's length of 4000 is too long for CHAR + VAT_COUNTRY TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CURRENCY_CODE CHAR(10), + AFFILIATE_ID CHAR(50), + AFFILIATE_NAME CHAR(50), + PORTAL_ID CHAR(50), + PORTAL_NAME CHAR(50), + CUSTOM_0 TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CUSTOM_1 TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CUSTOM_2 TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CUSTOM_3 TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CUSTOM_4 TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CUSTOM_5 TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CUSTOM_6 TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CUSTOM_7 TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CUSTOM_8 TEXT, -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR + CUSTOM_9 TEXT -- Changed to TEXT because eSellerate's length of 1000 is too long for CHAR +) ENGINE=InnoDB; + +-- OrderLines has information about each of the SKUs in the order. +CREATE TABLE OrderLines ( + SKU_ID CHAR(50) NOT NULL, + SKU_TITLE CHAR(255), + SHORT_DESCRIPTION CHAR(255), + QUANTITY INT, + UNIT_PRICE DECIMAL(10,2), + PLATFORM CHAR(50), + REGISTRATION_NAME CHAR(100), + PROMPTED_VALUE CHAR(255), + REGISTRATION_OTHER CHAR(255), + EXPIRATION_DATE CHAR(50), + SERIAL_NUMBER TEXT, -- raised this from 255 so I could put Aquatic Prime SNs in there. + PROCESSING_FEE DECIMAL(10,2), + SALES_TAX_AMOUNT DECIMAL(10,2), + AFFILIATE_COMMISION DECIMAL(10,2), + VOLUME_DISCOUNT DECIMAL(10,2), + CROSS_SELL_DISCOUNT DECIMAL(10,2), + UP_SELL_GROUP_ID INT, + UP_SELL_PARENT_SKU_ID CHAR(50), + + SN_LOOKUP_COUNT INT DEFAULT 0, -- number of times a specific SN has been looked up + + ORDER_NUMBER CHAR(50) NOT NULL, + + SN_DATE DATETIME, + + FOREIGN KEY(ORDER_NUMBER) REFERENCES OrderInfo(ORDER_NUMBER), + PRIMARY KEY(ORDER_NUMBER,SKU_ID) +) ENGINE=InnoDB; + +-- SN Lookup records: +-- Each lookup may affect more than one SN (if there's more than one SN with the same email address). +-- Each SN may have been looked up more than one time (which is why we keep a count). + +-- If someone is looking up SNs, they should receive all SNs associated with their email address. +-- If a particular SN has been looked up too many times, it should be locked out. + +-- Just a record that the SN was looked up +CREATE TABLE SNLookupHistoryEntry ( + LOOKUP_ID INT NOT NULL AUTO_INCREMENT, + PRIMARY KEY(LOOKUP_ID), + -- We don't use real referential integrity on SERIAL_NUMBER because you can't do that + -- with TEXT (no known key length), but the fixed-length string types are too short. + SERIAL_NUMBER TEXT NOT NULL REFERENCES OrderLines(SERIAL_NUMBER), + LOOKUP_DATE DATETIME, + SUCCESS TINYINT DEFAULT 0 +) ENGINE=InnoDB; + +-- Looking up SNs without views (since we're assuming MySQL, which is too stupid to support views in the +-- versions commonly found on web hosts). +-- Look up results for email address using the following SELECT. +-- If results found, + -- For each SN, + -- increment lookup count for the SN in OrderLines + -- if lookup count <= N, send SN + -- Create a new SNLookupHistory entry with "success" value depending on whether SN was sent + +-- Look up SNs using this view. +-- For each SN found: +-- Increment lookup_count +-- If lookup_count <= N, send SN +-- Create a new SNLookupHistory entry with "success" value depending on whether SN was sent +--CREATE VIEW SNLookup AS diff --git a/User/PHP/eSellerateXML.php b/User/PHP/eSellerateXML.php new file mode 100755 index 0000000..a2f817c --- /dev/null +++ b/User/PHP/eSellerateXML.php @@ -0,0 +1,163 @@ + + diff --git a/User/Python/AquaticPrime.py b/User/Python/AquaticPrime.py new file mode 100644 index 0000000..6293b3a --- /dev/null +++ b/User/Python/AquaticPrime.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +import os +import sha + +from base64 import decodestring as b64decode +from base64 import encodestring as b64encode +from binascii import unhexlify as hex2bin + +# These are some example generated keys for you convinience, replace the before you start selling your app ;-) +pubKey = u'0xE9DBF6A4F6B443282117C6D5E9255F6735DC45DBCB9FA3CABD0F082689B4A25504A2340E2F2F541BF2CE7987491EC541E8B5496BB6AF235F18B6C31F37CA68B430431E41611E93DCFBE40EB7D3C726E74B9D68B9867706A5E0CBD44E0B8863AAC3D2FDBF3CD57B10C3E90039E966F789CC8CBCB1CEBBD2EB95FF5F05E48F37A3' +privKey = u'0x9BE7F9C34F22D770160FD9E3F0C394EF793D83E7DD1517DC7E0A056F06786C38ADC1780974CA3812A1DEFBAF8614838145CE30F279CA1794BB248214CFDC45CC2EFAD1A84D0B8B442D71623486EC36DF6036A4AD8CD319743E7BCF0ECFEA8D0955B1305E42FE30F042D67A9317F10FF3CD2EDFB1D003896EF7791742199348AB' + +def hex2dec(s): + return int(s, 16) + +def dec2hex(n): + val = "%X" % n + while len(val) < 256: + val = '0' + val + return val + +def powmod(x,a,m): + r=1 + while a>0: + if a%2==1: r=(r*x)%m + a=a>>1; x=(x*x)%m + return r + +def reverse(s): + rs = "" + for x in s: + rs = x + rs + return rs + +def getSignature(information): + + keys = information.keys() + keys.sort() + + total = u''.join([information[key] for key in keys]).replace(u"'", u"'\\''") + + hash = sha.new(total.encode('utf-8')).hexdigest() + + paddedHash = '0001' + + for i in range(0, 105): + paddedHash += 'ff' + + paddedHash += '00' + hash + + decryptedSig = hex2dec(paddedHash) + + sig = powmod(decryptedSig, hex2dec(privKey), hex2dec(pubKey)) + sig = dec2hex(sig) + sig = hex2bin(sig) + sig = b64encode(sig) + + return sig + +def licenceData(license_info): + + # license_info should be a dict with all the licence items + + keys = license_info.keys() + keys.sort() + + signature = getSignature(license_info) + + licence_data = u"\n" + licence_data += u"\n" + licence_data += u"\n\n" + + for key in keys: + + licence_data += u"\t" + key + u"\n" + licence_data += u"\t" + license_info[key] + u"\n" + + licence_data += u"\tSignature\n" + licence_data += u"\t" + signature + u"\n" + licence_data += u"\n" + licence_data += u"\n" + + return licence_data + +print licenceData({u'name':u'koen'}) \ No newline at end of file diff --git a/User/Ruby (Experimental)/AquaticPrime.rb b/User/Ruby (Experimental)/AquaticPrime.rb new file mode 100644 index 0000000..6896520 --- /dev/null +++ b/User/Ruby (Experimental)/AquaticPrime.rb @@ -0,0 +1,78 @@ +#!/usr/bin/env ruby + +# Ruby implementation of AquaticPrime license generation. +# Written by John Labovitz . + +require 'digest/sha1' +require 'plist' +require 'base64' + + +module Math + + def self.powmod(x, a, m) + r = 1 + while a > 0 + if a % 2 == 1 + r = (r * x) % m + end + a = a >> 1 + x = (x * x) % m + end + r + end + +end + + +class AquaticPrime + + def initialize(pubKey, privKey) + @pubKey = pubKey + @privKey = privKey + end + + def signature(information) + + total = information.sort.map { |key, value| value }.join('') + + hash = Digest::SHA1.hexdigest(total) + hash = '0001' + ('ff' * 105) + '00' + hash + + sig = Math.powmod(hash.hex, @privKey.hex, @pubKey.hex) + + # Convert from a big number to a binary string. + sig = sig.to_s(16) + sig = ('0' * (256 - sig.length)) + sig + sig = sig.unpack('a2' * (sig.length/2)).map { |x| x.hex }.pack('c' * (sig.length/2)) + + sig + end + + def licence_data(license_info) + signed_license_info = license_info.dup + + # Sign the license info. + # If a value in the plist is a StringIO object, it handily ends up as a base64-encoded key + signed_license_info['Signature'] = StringIO.new(signature(license_info)) + + signed_license_info.to_plist + end + +end + + +if $0 == __FILE__ + + # sample keys + pubKey = '0xE9DBF6A4F6B443282117C6D5E9255F6735DC45DBCB9FA3CABD0F082689B4A25504A2340E2F2F541BF2CE7987491EC541E8B5496BB6AF235F18B6C31F37CA68B430431E41611E93DCFBE40EB7D3C726E74B9D68B9867706A5E0CBD44E0B8863AAC3D2FDBF3CD57B10C3E90039E966F789CC8CBCB1CEBBD2EB95FF5F05E48F37A3' + privKey = '0x9BE7F9C34F22D770160FD9E3F0C394EF793D83E7DD1517DC7E0A056F06786C38ADC1780974CA3812A1DEFBAF8614838145CE30F279CA1794BB248214CFDC45CC2EFAD1A84D0B8B442D71623486EC36DF6036A4AD8CD319743E7BCF0ECFEA8D0955B1305E42FE30F042D67A9317F10FF3CD2EDFB1D003896EF7791742199348AB' + + aquatic_prime = AquaticPrime.new(pubKey, privKey) + + license = { + 'name' => 'koen' + } + + puts aquatic_prime.licence_data(license) +end \ No newline at end of file diff --git a/User/STL/AquaticPrime.cpp b/User/STL/AquaticPrime.cpp new file mode 100644 index 0000000..4dd6cd3 --- /dev/null +++ b/User/STL/AquaticPrime.cpp @@ -0,0 +1,299 @@ +// +// AquaticPrime.cpp +// AquaticPrime STL Implementation +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "AquaticPrime.h" +#include "tinyxml.h" +#include +#include + +static RSA *rsaKey; +static std::string hash; +static std::list blacklist; + +// utilities +inline char ToLower(char in) +{ + return (char)tolower((int)in); +} + +const char* CreateCString(std::string output, ...) +{ + static char text[256]; + va_list ap; + + va_start(ap, output); // Parses The String For Variables + vsprintf(text, output.c_str(), ap); // And Converts Symbols To Actual Numbers + va_end(ap); + + return (const char*)text; +} + +bool APSetKey(std::string key) +{ + hash = std::string(""); + + // Create a new key + rsaKey = RSA_new(); + + // Public exponent is always 3 + BN_hex2bn(&rsaKey->e, "3"); + + std::string mutableKey = key; + + // Determine if we have a hex or decimal key + std::transform(mutableKey.begin(), mutableKey.end(), mutableKey.begin(), ToLower); // make mutableKey lowercase + if(std::string(mutableKey, 0, 2) == "0x") + { + mutableKey = std::string(mutableKey, 2, mutableKey.length()); + BN_hex2bn(&rsaKey->n, mutableKey.c_str()); + } + else + { + BN_dec2bn(&rsaKey->n, mutableKey.c_str()); + } + + return true; +} + +std::string APHash(void) +{ + return hash; +} + +void APSetHash(std::string newHash) +{ + hash = newHash; +} + +// Set the entire blacklist array, removing any existing entries +void APSetBlacklist(std::list hashArray) +{ + blacklist = hashArray; +} + +void APBlacklistAdd(std::string blacklistEntry) +{ + blacklist.push_back(blacklistEntry); +} + +std::map APCreateDictionaryForLicenseData(std::map data) +{ + if (!rsaKey->n || !rsaKey->e) + { + std::map empty; + printf("0\n"); + return empty; + } + + // Load the signature + unsigned char sigBytes[128]; + std::map::iterator signature = data.find("Signature"); + + if(signature == data.end()) + { + std::map empty; +// printf("1\n"); + return empty; + } + else + { + int returnVal = b64::b64_decode(data["Signature"].c_str(), data["Signature"].length(), sigBytes, 129); + + if(returnVal == 0) + { + std::map empty; + // printf("1.5\n"); + return empty; + } + + data.erase(signature); + } + + // Decrypt the signature + unsigned char checkDigest[128] = {0}; + if (RSA_public_decrypt(128, sigBytes, checkDigest, rsaKey, RSA_PKCS1_PADDING) != SHA_DIGEST_LENGTH) + { + std::map empty; +// printf("2\n"); + return empty; + } + + // Get the license hash + std::string hashCheck; + int hashIndex; + for (hashIndex = 0; hashIndex < SHA_DIGEST_LENGTH; hashIndex++) + hashCheck += CreateCString("%02x", checkDigest[hashIndex]); + APSetHash(hashCheck); + + if (blacklist.size() > 0) + { + // $$ is this right? + for(std::list::iterator b = blacklist.begin(); b != blacklist.end(); ++b) + { + if(data.find((*b)) != data.end()) + { + std::map empty; +// printf("3\n"); + return empty; + } + } + } + + // Get the number of elements + int count = data.size(); + // Load the keys and build up the key array +// std::list keyArray; + std::string keys[count]; + + int counter = 0; + for(std::map::iterator d = data.begin(); d != data.end(); ++d) + { + keys[counter] = (*d).first; + ++counter; + } + + // Sort the array ( $$ why? for cleanliness reasons? ) +// int context = kCFCompareCaseInsensitive; +// CFArraySortValues(keyArray, CFRangeMake(0, count), (CFComparatorFunction)CFStringCompare, &context); + + // Setup up the hash context + SHA_CTX ctx; + SHA1_Init(&ctx); + // Convert into UTF8 strings + for(int i = 0; i < count; i++) + { + std::string key = keys[i]; // $$ convert this to keyArray later + std::string value = data[key]; + + // Account for the null terminator + SHA1_Update(&ctx, value.c_str(), strlen(value.c_str())); + } + unsigned char digest[SHA_DIGEST_LENGTH]; + SHA1_Final(digest, &ctx); + + // Check if the signature is a match + for (int i = 0; i < SHA_DIGEST_LENGTH; i++) + { + if (checkDigest[i] ^ digest[i]) + { + std::map empty; +// printf("4\n"); + return empty; + } + } + + // If it's a match, we return the dictionary; otherwise, we never reach this + return data; +} + +std::map APCreateDictionaryForLicenseFile(std::string path) +{ + std::map xmlData; + + TiXmlNode *node = 0; + TiXmlDocument licenseFile(path.c_str()); + licenseFile.LoadFile(); + + node = licenseFile.FirstChild("plist"); + if(node == NULL) return xmlData; + node = node->FirstChild("dict"); + if(node == NULL) return xmlData; + + do + { + // + if(std::string(node->ToElement()->Value()) == std::string("dict")) + { + std::string key, data; + TiXmlNode *innerNode = node->FirstChild(); + while(innerNode != NULL) + { + // + if(std::string(innerNode->ToElement()->Value()) == std::string("key")) + { + key = innerNode->ToElement()->FirstChild()->Value(); +// printf("key %s\n", key.c_str()); + } + // + else if(std::string(innerNode->ToElement()->Value()) == std::string("string")) + { + data = innerNode->ToElement()->FirstChild()->Value(); + xmlData[key] = data; +// printf("string %s %s\n", key.c_str(), data.c_str()); + } + // + else if(std::string(innerNode->ToElement()->Value()) == std::string("data")) + { + data = innerNode->ToElement()->FirstChild()->Value(); + + if(key == "Signature") // get rid of any spaces + { + std::vector spaces; + for(std::string::iterator d = data.begin(); d != data.end(); ++d) + { + if((*d) == ' ') + spaces.push_back(d); + } + + for(uint s=0; s < spaces.size(); ++s) + data.erase(spaces[s] - (s)); + } + + xmlData[key] = data; +// printf("data %s %s\n", key.c_str(), data.c_str()); + } + + innerNode = innerNode->NextSibling(); + } + } + + node = node->NextSibling(); + } while(node != NULL); + + std::map licenseDictionary = APCreateDictionaryForLicenseData(xmlData); + + return licenseDictionary; +} + +bool APVerifyLicenseData(std::map data) +{ + std::map licenseDictionary = APCreateDictionaryForLicenseData(data); + + if (licenseDictionary.size() > 0) + return true; + else + return false; +} + +bool APVerifyLicenseFile(std::string path) +{ + std::map licenseDictionary = APCreateDictionaryForLicenseFile(path); + + if (licenseDictionary.size() > 0) + return true; + else + return false; +} diff --git a/User/STL/AquaticPrime.h b/User/STL/AquaticPrime.h new file mode 100644 index 0000000..56d1c29 --- /dev/null +++ b/User/STL/AquaticPrime.h @@ -0,0 +1,45 @@ +// +// AquaticPrime.h +// AquaticPrime STL Implementation +// +// Copyright (c) 2005, Lucas Newman +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// ¥Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// ¥Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// ¥Neither the name of the Aquatic nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include +#include + +// Set the key - must be called first +bool APSetKey(std::string key); + +// Validating & extracting licenses +std::map APCreateDictionaryForLicenseData(std::map data); +std::map APCreateDictionaryForLicenseFile(std::string path); +bool APVerifyLicenseData(std::map data); +bool APVerifyLicenseFile(std::string path); + +std::string APHash(void); +void APBlacklistAdd(std::string blacklistEntry); +void APSetBlacklist(std::list hashArray); +