├── .gitignore ├── LICENSE ├── Makefile ├── PGPFormat.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── PGPFormat ├── AsciiArmor.swift ├── Info.plist ├── LiteralData.swift ├── Messagable.swift ├── Message.swift ├── OnePassSignature.swift ├── PGPFormat.h ├── PGPFormat.swift ├── Packet.swift ├── Packetable.swift ├── PublicKey.swift ├── PublicKeyData.swift ├── Signable.swift ├── Signature.swift ├── SignatureSubpacket.swift ├── SignatureSubpacketTypes.swift ├── SignatureSubpacketable.swift ├── SignedAttachedBinaryDocument.swift ├── SignedBinaryDocument.swift ├── SignedPublicKeyIdentity.swift ├── UserID.swift └── Util.swift ├── PGPFormatTests ├── Info.plist ├── PGPFormatTests.swift ├── attached_signature.txt ├── pubkey1.txt ├── pubkey2.txt ├── pubkey3.txt ├── pubkey4.txt ├── signature.txt └── signed_raw.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.xcuserdatad/ 2 | *.xcodeproj/project.xcworkspace/xcshareddata/ 3 | *.xcodeproj/project.xcworkspace/contents.xcworkspacedata 4 | xcodebuild.log 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 KryptCo, Inc. 2 | All Rights Reserved. 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | check: 2 | set -o pipefail && xcodebuild test -project PGPFormat.xcodeproj -scheme PGPFormatTests -destination 'platform=iOS Simulator,name=iPhone 7' | tee xcodebuild.log | xcpretty 3 | -------------------------------------------------------------------------------- /PGPFormat.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FA12EA8C1ECD1EA5001603BF /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA12EA8B1ECD1EA5001603BF /* Security.framework */; }; 11 | FA12EA8F1ECE100C001603BF /* SignatureSubpacket.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA12EA8E1ECE100C001603BF /* SignatureSubpacket.swift */; }; 12 | FA12EA931ECF5404001603BF /* SignatureSubpacketTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA12EA921ECF5404001603BF /* SignatureSubpacketTypes.swift */; }; 13 | FA12EA991ECF9197001603BF /* SignedPublicKeyIdentity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA12EA981ECF9197001603BF /* SignedPublicKeyIdentity.swift */; }; 14 | FA12EAB71ED000D7001603BF /* pubkey2.txt in Resources */ = {isa = PBXBuildFile; fileRef = FA12EAB61ED000D7001603BF /* pubkey2.txt */; }; 15 | FA12EAB91ED0283D001603BF /* pubkey3.txt in Resources */ = {isa = PBXBuildFile; fileRef = FA12EAB81ED0283D001603BF /* pubkey3.txt */; }; 16 | FA12EAC31ED1442D001603BF /* signature.txt in Resources */ = {isa = PBXBuildFile; fileRef = FA12EAC21ED1442D001603BF /* signature.txt */; }; 17 | FA12EAC51ED14482001603BF /* signed_raw.txt in Resources */ = {isa = PBXBuildFile; fileRef = FA12EAC41ED14482001603BF /* signed_raw.txt */; }; 18 | FA2151DE1EF9A4520090F398 /* attached_signature.txt in Resources */ = {isa = PBXBuildFile; fileRef = FA2151DD1EF9A4520090F398 /* attached_signature.txt */; }; 19 | FA2949E11EDD180800A5C70B /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA2949E01EDD180800A5C70B /* Message.swift */; }; 20 | FA5577EE1EE3C93000D1DA99 /* SignatureSubpacketable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA5577ED1EE3C93000D1DA99 /* SignatureSubpacketable.swift */; }; 21 | FAAEAF5B1FBE966A0051E9F2 /* pubkey4.txt in Resources */ = {isa = PBXBuildFile; fileRef = FAAEAF5A1FBE966A0051E9F2 /* pubkey4.txt */; }; 22 | FACE33BD1EC9E779003EC6C0 /* PGPFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = FACE33BB1EC9E779003EC6C0 /* PGPFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; 23 | FACE33C81EC9E806003EC6C0 /* Packet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACE33C31EC9E806003EC6C0 /* Packet.swift */; }; 24 | FACE33C91EC9E806003EC6C0 /* PublicKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACE33C41EC9E806003EC6C0 /* PublicKey.swift */; }; 25 | FACE33CA1EC9E806003EC6C0 /* Signature.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACE33C51EC9E806003EC6C0 /* Signature.swift */; }; 26 | FACE33CB1EC9E806003EC6C0 /* UserID.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACE33C61EC9E806003EC6C0 /* UserID.swift */; }; 27 | FACE33CC1EC9E806003EC6C0 /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACE33C71EC9E806003EC6C0 /* Util.swift */; }; 28 | FACE33CE1EC9E81A003EC6C0 /* PGPFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACE33CD1EC9E81A003EC6C0 /* PGPFormat.swift */; }; 29 | FACE33D01EC9E88D003EC6C0 /* AsciiArmor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FACE33CF1EC9E88D003EC6C0 /* AsciiArmor.swift */; }; 30 | FAD667631ED2119D00A4430D /* SignedBinaryDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD667621ED2119D00A4430D /* SignedBinaryDocument.swift */; }; 31 | FAD667671ED22E7800A4430D /* Signable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAD667661ED22E7800A4430D /* Signable.swift */; }; 32 | FADDE2761EF9B3D00049AA67 /* OnePassSignature.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADDE2751EF9B3D00049AA67 /* OnePassSignature.swift */; }; 33 | FADDE2781EF9BAED0049AA67 /* LiteralData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADDE2771EF9BAED0049AA67 /* LiteralData.swift */; }; 34 | FADDE27A1EF9C5450049AA67 /* SignedAttachedBinaryDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADDE2791EF9C5450049AA67 /* SignedAttachedBinaryDocument.swift */; }; 35 | FADE9B261EE368240053EC08 /* Messagable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADE9B251EE368240053EC08 /* Messagable.swift */; }; 36 | FADE9B281EE381470053EC08 /* Packetable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADE9B271EE381470053EC08 /* Packetable.swift */; }; 37 | FADE9B2C1EE3B64B0053EC08 /* PublicKeyData.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADE9B2B1EE3B64B0053EC08 /* PublicKeyData.swift */; }; 38 | FAEA70591ECC192D00920AAB /* PGPFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAEA70581ECC192D00920AAB /* PGPFormatTests.swift */; }; 39 | FAEA705B1ECC192D00920AAB /* PGPFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FACE33B81EC9E779003EC6C0 /* PGPFormat.framework */; }; 40 | FAEA70621ECC197400920AAB /* pubkey1.txt in Resources */ = {isa = PBXBuildFile; fileRef = FAEA70611ECC197400920AAB /* pubkey1.txt */; }; 41 | /* End PBXBuildFile section */ 42 | 43 | /* Begin PBXContainerItemProxy section */ 44 | FAEA705C1ECC192D00920AAB /* PBXContainerItemProxy */ = { 45 | isa = PBXContainerItemProxy; 46 | containerPortal = FACE33AF1EC9E779003EC6C0 /* Project object */; 47 | proxyType = 1; 48 | remoteGlobalIDString = FACE33B71EC9E779003EC6C0; 49 | remoteInfo = PGPFormat; 50 | }; 51 | /* End PBXContainerItemProxy section */ 52 | 53 | /* Begin PBXFileReference section */ 54 | FA12EA8B1ECD1EA5001603BF /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 55 | FA12EA8E1ECE100C001603BF /* SignatureSubpacket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignatureSubpacket.swift; sourceTree = ""; }; 56 | FA12EA921ECF5404001603BF /* SignatureSubpacketTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignatureSubpacketTypes.swift; sourceTree = ""; }; 57 | FA12EA981ECF9197001603BF /* SignedPublicKeyIdentity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignedPublicKeyIdentity.swift; sourceTree = ""; }; 58 | FA12EAB61ED000D7001603BF /* pubkey2.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = pubkey2.txt; sourceTree = ""; }; 59 | FA12EAB81ED0283D001603BF /* pubkey3.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = pubkey3.txt; sourceTree = ""; }; 60 | FA12EAC21ED1442D001603BF /* signature.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = signature.txt; sourceTree = ""; }; 61 | FA12EAC41ED14482001603BF /* signed_raw.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = signed_raw.txt; sourceTree = ""; }; 62 | FA2151DD1EF9A4520090F398 /* attached_signature.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = attached_signature.txt; sourceTree = ""; }; 63 | FA2949E01EDD180800A5C70B /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; 64 | FA5577ED1EE3C93000D1DA99 /* SignatureSubpacketable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignatureSubpacketable.swift; sourceTree = ""; }; 65 | FAAEAF5A1FBE966A0051E9F2 /* pubkey4.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = pubkey4.txt; sourceTree = ""; }; 66 | FACE33B81EC9E779003EC6C0 /* PGPFormat.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PGPFormat.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | FACE33BB1EC9E779003EC6C0 /* PGPFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PGPFormat.h; sourceTree = ""; }; 68 | FACE33BC1EC9E779003EC6C0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 69 | FACE33C31EC9E806003EC6C0 /* Packet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Packet.swift; sourceTree = ""; }; 70 | FACE33C41EC9E806003EC6C0 /* PublicKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicKey.swift; sourceTree = ""; }; 71 | FACE33C51EC9E806003EC6C0 /* Signature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signature.swift; sourceTree = ""; }; 72 | FACE33C61EC9E806003EC6C0 /* UserID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserID.swift; sourceTree = ""; }; 73 | FACE33C71EC9E806003EC6C0 /* Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = ""; }; 74 | FACE33CD1EC9E81A003EC6C0 /* PGPFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PGPFormat.swift; sourceTree = ""; }; 75 | FACE33CF1EC9E88D003EC6C0 /* AsciiArmor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsciiArmor.swift; sourceTree = ""; }; 76 | FAD667621ED2119D00A4430D /* SignedBinaryDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignedBinaryDocument.swift; sourceTree = ""; }; 77 | FAD667661ED22E7800A4430D /* Signable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Signable.swift; sourceTree = ""; }; 78 | FADDE2751EF9B3D00049AA67 /* OnePassSignature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnePassSignature.swift; sourceTree = ""; }; 79 | FADDE2771EF9BAED0049AA67 /* LiteralData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LiteralData.swift; sourceTree = ""; }; 80 | FADDE2791EF9C5450049AA67 /* SignedAttachedBinaryDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignedAttachedBinaryDocument.swift; sourceTree = ""; }; 81 | FADE9B251EE368240053EC08 /* Messagable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Messagable.swift; sourceTree = ""; }; 82 | FADE9B271EE381470053EC08 /* Packetable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Packetable.swift; sourceTree = ""; }; 83 | FADE9B2B1EE3B64B0053EC08 /* PublicKeyData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicKeyData.swift; sourceTree = ""; }; 84 | FAEA70561ECC192D00920AAB /* PGPFormatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PGPFormatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 85 | FAEA70581ECC192D00920AAB /* PGPFormatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PGPFormatTests.swift; sourceTree = ""; }; 86 | FAEA705A1ECC192D00920AAB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 87 | FAEA70611ECC197400920AAB /* pubkey1.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = pubkey1.txt; sourceTree = ""; }; 88 | /* End PBXFileReference section */ 89 | 90 | /* Begin PBXFrameworksBuildPhase section */ 91 | FACE33B41EC9E779003EC6C0 /* Frameworks */ = { 92 | isa = PBXFrameworksBuildPhase; 93 | buildActionMask = 2147483647; 94 | files = ( 95 | FA12EA8C1ECD1EA5001603BF /* Security.framework in Frameworks */, 96 | ); 97 | runOnlyForDeploymentPostprocessing = 0; 98 | }; 99 | FAEA70531ECC192D00920AAB /* Frameworks */ = { 100 | isa = PBXFrameworksBuildPhase; 101 | buildActionMask = 2147483647; 102 | files = ( 103 | FAEA705B1ECC192D00920AAB /* PGPFormat.framework in Frameworks */, 104 | ); 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | /* End PBXFrameworksBuildPhase section */ 108 | 109 | /* Begin PBXGroup section */ 110 | FA12EA8A1ECD1EA5001603BF /* Frameworks */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | FA12EA8B1ECD1EA5001603BF /* Security.framework */, 114 | ); 115 | name = Frameworks; 116 | sourceTree = ""; 117 | }; 118 | FA5577EF1EE3CA8B00D1DA99 /* Packets */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | FACE33C31EC9E806003EC6C0 /* Packet.swift */, 122 | FADE9B271EE381470053EC08 /* Packetable.swift */, 123 | FACE33C41EC9E806003EC6C0 /* PublicKey.swift */, 124 | FADE9B2B1EE3B64B0053EC08 /* PublicKeyData.swift */, 125 | FACE33C61EC9E806003EC6C0 /* UserID.swift */, 126 | FACE33C51EC9E806003EC6C0 /* Signature.swift */, 127 | FA12EA8E1ECE100C001603BF /* SignatureSubpacket.swift */, 128 | FA5577ED1EE3C93000D1DA99 /* SignatureSubpacketable.swift */, 129 | FA12EA921ECF5404001603BF /* SignatureSubpacketTypes.swift */, 130 | FADDE2751EF9B3D00049AA67 /* OnePassSignature.swift */, 131 | FADDE2771EF9BAED0049AA67 /* LiteralData.swift */, 132 | ); 133 | name = Packets; 134 | sourceTree = ""; 135 | }; 136 | FA5577F01EE3CA9500D1DA99 /* Signable */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | FAD667661ED22E7800A4430D /* Signable.swift */, 140 | FA12EA981ECF9197001603BF /* SignedPublicKeyIdentity.swift */, 141 | FAD667621ED2119D00A4430D /* SignedBinaryDocument.swift */, 142 | FADDE2791EF9C5450049AA67 /* SignedAttachedBinaryDocument.swift */, 143 | ); 144 | name = Signable; 145 | sourceTree = ""; 146 | }; 147 | FA5577F11EE3CAA300D1DA99 /* Message */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | FA2949E01EDD180800A5C70B /* Message.swift */, 151 | FADE9B251EE368240053EC08 /* Messagable.swift */, 152 | FACE33CF1EC9E88D003EC6C0 /* AsciiArmor.swift */, 153 | ); 154 | name = Message; 155 | sourceTree = ""; 156 | }; 157 | FACE33AE1EC9E779003EC6C0 = { 158 | isa = PBXGroup; 159 | children = ( 160 | FACE33BA1EC9E779003EC6C0 /* PGPFormat */, 161 | FAEA70571ECC192D00920AAB /* PGPFormatTests */, 162 | FACE33B91EC9E779003EC6C0 /* Products */, 163 | FA12EA8A1ECD1EA5001603BF /* Frameworks */, 164 | ); 165 | sourceTree = ""; 166 | }; 167 | FACE33B91EC9E779003EC6C0 /* Products */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | FACE33B81EC9E779003EC6C0 /* PGPFormat.framework */, 171 | FAEA70561ECC192D00920AAB /* PGPFormatTests.xctest */, 172 | ); 173 | name = Products; 174 | sourceTree = ""; 175 | }; 176 | FACE33BA1EC9E779003EC6C0 /* PGPFormat */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | FACE33BC1EC9E779003EC6C0 /* Info.plist */, 180 | FACE33BB1EC9E779003EC6C0 /* PGPFormat.h */, 181 | FACE33CD1EC9E81A003EC6C0 /* PGPFormat.swift */, 182 | FA5577F11EE3CAA300D1DA99 /* Message */, 183 | FA5577EF1EE3CA8B00D1DA99 /* Packets */, 184 | FA5577F01EE3CA9500D1DA99 /* Signable */, 185 | FACE33C71EC9E806003EC6C0 /* Util.swift */, 186 | ); 187 | path = PGPFormat; 188 | sourceTree = ""; 189 | }; 190 | FAEA70571ECC192D00920AAB /* PGPFormatTests */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | FAEA70581ECC192D00920AAB /* PGPFormatTests.swift */, 194 | FAEA70611ECC197400920AAB /* pubkey1.txt */, 195 | FA12EAB61ED000D7001603BF /* pubkey2.txt */, 196 | FA12EAB81ED0283D001603BF /* pubkey3.txt */, 197 | FAAEAF5A1FBE966A0051E9F2 /* pubkey4.txt */, 198 | FA12EAC21ED1442D001603BF /* signature.txt */, 199 | FA2151DD1EF9A4520090F398 /* attached_signature.txt */, 200 | FA12EAC41ED14482001603BF /* signed_raw.txt */, 201 | FAEA705A1ECC192D00920AAB /* Info.plist */, 202 | ); 203 | path = PGPFormatTests; 204 | sourceTree = ""; 205 | }; 206 | /* End PBXGroup section */ 207 | 208 | /* Begin PBXHeadersBuildPhase section */ 209 | FACE33B51EC9E779003EC6C0 /* Headers */ = { 210 | isa = PBXHeadersBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | FACE33BD1EC9E779003EC6C0 /* PGPFormat.h in Headers */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXHeadersBuildPhase section */ 218 | 219 | /* Begin PBXNativeTarget section */ 220 | FACE33B71EC9E779003EC6C0 /* PGPFormat */ = { 221 | isa = PBXNativeTarget; 222 | buildConfigurationList = FACE33C01EC9E779003EC6C0 /* Build configuration list for PBXNativeTarget "PGPFormat" */; 223 | buildPhases = ( 224 | FACE33B31EC9E779003EC6C0 /* Sources */, 225 | FACE33B41EC9E779003EC6C0 /* Frameworks */, 226 | FACE33B51EC9E779003EC6C0 /* Headers */, 227 | FACE33B61EC9E779003EC6C0 /* Resources */, 228 | ); 229 | buildRules = ( 230 | ); 231 | dependencies = ( 232 | ); 233 | name = PGPFormat; 234 | productName = PGPFormat; 235 | productReference = FACE33B81EC9E779003EC6C0 /* PGPFormat.framework */; 236 | productType = "com.apple.product-type.framework"; 237 | }; 238 | FAEA70551ECC192D00920AAB /* PGPFormatTests */ = { 239 | isa = PBXNativeTarget; 240 | buildConfigurationList = FAEA705E1ECC192D00920AAB /* Build configuration list for PBXNativeTarget "PGPFormatTests" */; 241 | buildPhases = ( 242 | FAEA70521ECC192D00920AAB /* Sources */, 243 | FAEA70531ECC192D00920AAB /* Frameworks */, 244 | FAEA70541ECC192D00920AAB /* Resources */, 245 | ); 246 | buildRules = ( 247 | ); 248 | dependencies = ( 249 | FAEA705D1ECC192D00920AAB /* PBXTargetDependency */, 250 | ); 251 | name = PGPFormatTests; 252 | productName = PGPFormatTests; 253 | productReference = FAEA70561ECC192D00920AAB /* PGPFormatTests.xctest */; 254 | productType = "com.apple.product-type.bundle.unit-test"; 255 | }; 256 | /* End PBXNativeTarget section */ 257 | 258 | /* Begin PBXProject section */ 259 | FACE33AF1EC9E779003EC6C0 /* Project object */ = { 260 | isa = PBXProject; 261 | attributes = { 262 | LastSwiftUpdateCheck = 0830; 263 | LastUpgradeCheck = 1020; 264 | ORGANIZATIONNAME = "KryptCo, Inc."; 265 | TargetAttributes = { 266 | FACE33B71EC9E779003EC6C0 = { 267 | CreatedOnToolsVersion = 8.3.2; 268 | DevelopmentTeam = W7AMYM5LPN; 269 | LastSwiftMigration = 0910; 270 | ProvisioningStyle = Automatic; 271 | }; 272 | FAEA70551ECC192D00920AAB = { 273 | CreatedOnToolsVersion = 8.3.2; 274 | DevelopmentTeam = W7AMYM5LPN; 275 | LastSwiftMigration = 0910; 276 | ProvisioningStyle = Automatic; 277 | }; 278 | }; 279 | }; 280 | buildConfigurationList = FACE33B21EC9E779003EC6C0 /* Build configuration list for PBXProject "PGPFormat" */; 281 | compatibilityVersion = "Xcode 3.2"; 282 | developmentRegion = en; 283 | hasScannedForEncodings = 0; 284 | knownRegions = ( 285 | en, 286 | Base, 287 | ); 288 | mainGroup = FACE33AE1EC9E779003EC6C0; 289 | productRefGroup = FACE33B91EC9E779003EC6C0 /* Products */; 290 | projectDirPath = ""; 291 | projectRoot = ""; 292 | targets = ( 293 | FACE33B71EC9E779003EC6C0 /* PGPFormat */, 294 | FAEA70551ECC192D00920AAB /* PGPFormatTests */, 295 | ); 296 | }; 297 | /* End PBXProject section */ 298 | 299 | /* Begin PBXResourcesBuildPhase section */ 300 | FACE33B61EC9E779003EC6C0 /* Resources */ = { 301 | isa = PBXResourcesBuildPhase; 302 | buildActionMask = 2147483647; 303 | files = ( 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | FAEA70541ECC192D00920AAB /* Resources */ = { 308 | isa = PBXResourcesBuildPhase; 309 | buildActionMask = 2147483647; 310 | files = ( 311 | FAEA70621ECC197400920AAB /* pubkey1.txt in Resources */, 312 | FA12EAB71ED000D7001603BF /* pubkey2.txt in Resources */, 313 | FA12EAB91ED0283D001603BF /* pubkey3.txt in Resources */, 314 | FA12EAC31ED1442D001603BF /* signature.txt in Resources */, 315 | FA12EAC51ED14482001603BF /* signed_raw.txt in Resources */, 316 | FAAEAF5B1FBE966A0051E9F2 /* pubkey4.txt in Resources */, 317 | FA2151DE1EF9A4520090F398 /* attached_signature.txt in Resources */, 318 | ); 319 | runOnlyForDeploymentPostprocessing = 0; 320 | }; 321 | /* End PBXResourcesBuildPhase section */ 322 | 323 | /* Begin PBXSourcesBuildPhase section */ 324 | FACE33B31EC9E779003EC6C0 /* Sources */ = { 325 | isa = PBXSourcesBuildPhase; 326 | buildActionMask = 2147483647; 327 | files = ( 328 | FADE9B2C1EE3B64B0053EC08 /* PublicKeyData.swift in Sources */, 329 | FA5577EE1EE3C93000D1DA99 /* SignatureSubpacketable.swift in Sources */, 330 | FA12EA991ECF9197001603BF /* SignedPublicKeyIdentity.swift in Sources */, 331 | FACE33D01EC9E88D003EC6C0 /* AsciiArmor.swift in Sources */, 332 | FAD667671ED22E7800A4430D /* Signable.swift in Sources */, 333 | FADE9B261EE368240053EC08 /* Messagable.swift in Sources */, 334 | FADDE2781EF9BAED0049AA67 /* LiteralData.swift in Sources */, 335 | FADE9B281EE381470053EC08 /* Packetable.swift in Sources */, 336 | FACE33CA1EC9E806003EC6C0 /* Signature.swift in Sources */, 337 | FAD667631ED2119D00A4430D /* SignedBinaryDocument.swift in Sources */, 338 | FACE33C91EC9E806003EC6C0 /* PublicKey.swift in Sources */, 339 | FA2949E11EDD180800A5C70B /* Message.swift in Sources */, 340 | FACE33CE1EC9E81A003EC6C0 /* PGPFormat.swift in Sources */, 341 | FADDE27A1EF9C5450049AA67 /* SignedAttachedBinaryDocument.swift in Sources */, 342 | FACE33CC1EC9E806003EC6C0 /* Util.swift in Sources */, 343 | FACE33C81EC9E806003EC6C0 /* Packet.swift in Sources */, 344 | FACE33CB1EC9E806003EC6C0 /* UserID.swift in Sources */, 345 | FA12EA931ECF5404001603BF /* SignatureSubpacketTypes.swift in Sources */, 346 | FADDE2761EF9B3D00049AA67 /* OnePassSignature.swift in Sources */, 347 | FA12EA8F1ECE100C001603BF /* SignatureSubpacket.swift in Sources */, 348 | ); 349 | runOnlyForDeploymentPostprocessing = 0; 350 | }; 351 | FAEA70521ECC192D00920AAB /* Sources */ = { 352 | isa = PBXSourcesBuildPhase; 353 | buildActionMask = 2147483647; 354 | files = ( 355 | FAEA70591ECC192D00920AAB /* PGPFormatTests.swift in Sources */, 356 | ); 357 | runOnlyForDeploymentPostprocessing = 0; 358 | }; 359 | /* End PBXSourcesBuildPhase section */ 360 | 361 | /* Begin PBXTargetDependency section */ 362 | FAEA705D1ECC192D00920AAB /* PBXTargetDependency */ = { 363 | isa = PBXTargetDependency; 364 | target = FACE33B71EC9E779003EC6C0 /* PGPFormat */; 365 | targetProxy = FAEA705C1ECC192D00920AAB /* PBXContainerItemProxy */; 366 | }; 367 | /* End PBXTargetDependency section */ 368 | 369 | /* Begin XCBuildConfiguration section */ 370 | FACE33BE1EC9E779003EC6C0 /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ALWAYS_SEARCH_USER_PATHS = NO; 374 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 375 | CLANG_ANALYZER_NONNULL = YES; 376 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 377 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 378 | CLANG_CXX_LIBRARY = "libc++"; 379 | CLANG_ENABLE_MODULES = YES; 380 | CLANG_ENABLE_OBJC_ARC = YES; 381 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 382 | CLANG_WARN_BOOL_CONVERSION = YES; 383 | CLANG_WARN_COMMA = YES; 384 | CLANG_WARN_CONSTANT_CONVERSION = YES; 385 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 386 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 387 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 388 | CLANG_WARN_EMPTY_BODY = YES; 389 | CLANG_WARN_ENUM_CONVERSION = YES; 390 | CLANG_WARN_INFINITE_RECURSION = YES; 391 | CLANG_WARN_INT_CONVERSION = YES; 392 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 393 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 394 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 395 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 396 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 397 | CLANG_WARN_STRICT_PROTOTYPES = YES; 398 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 399 | CLANG_WARN_UNREACHABLE_CODE = YES; 400 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 401 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 402 | COPY_PHASE_STRIP = NO; 403 | CURRENT_PROJECT_VERSION = 1; 404 | DEBUG_INFORMATION_FORMAT = dwarf; 405 | ENABLE_STRICT_OBJC_MSGSEND = YES; 406 | ENABLE_TESTABILITY = YES; 407 | GCC_C_LANGUAGE_STANDARD = gnu99; 408 | GCC_DYNAMIC_NO_PIC = NO; 409 | GCC_NO_COMMON_BLOCKS = YES; 410 | GCC_OPTIMIZATION_LEVEL = 0; 411 | GCC_PREPROCESSOR_DEFINITIONS = ( 412 | "DEBUG=1", 413 | "$(inherited)", 414 | ); 415 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 416 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 417 | GCC_WARN_UNDECLARED_SELECTOR = YES; 418 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 419 | GCC_WARN_UNUSED_FUNCTION = YES; 420 | GCC_WARN_UNUSED_VARIABLE = YES; 421 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 422 | MTL_ENABLE_DEBUG_INFO = YES; 423 | ONLY_ACTIVE_ARCH = YES; 424 | SDKROOT = iphoneos; 425 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 426 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 427 | TARGETED_DEVICE_FAMILY = "1,2"; 428 | VERSIONING_SYSTEM = "apple-generic"; 429 | VERSION_INFO_PREFIX = ""; 430 | }; 431 | name = Debug; 432 | }; 433 | FACE33BF1EC9E779003EC6C0 /* Release */ = { 434 | isa = XCBuildConfiguration; 435 | buildSettings = { 436 | ALWAYS_SEARCH_USER_PATHS = NO; 437 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 438 | CLANG_ANALYZER_NONNULL = YES; 439 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 440 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 441 | CLANG_CXX_LIBRARY = "libc++"; 442 | CLANG_ENABLE_MODULES = YES; 443 | CLANG_ENABLE_OBJC_ARC = YES; 444 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 445 | CLANG_WARN_BOOL_CONVERSION = YES; 446 | CLANG_WARN_COMMA = YES; 447 | CLANG_WARN_CONSTANT_CONVERSION = YES; 448 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 449 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 450 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 451 | CLANG_WARN_EMPTY_BODY = YES; 452 | CLANG_WARN_ENUM_CONVERSION = YES; 453 | CLANG_WARN_INFINITE_RECURSION = YES; 454 | CLANG_WARN_INT_CONVERSION = YES; 455 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 456 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 457 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 458 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 459 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 460 | CLANG_WARN_STRICT_PROTOTYPES = YES; 461 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 462 | CLANG_WARN_UNREACHABLE_CODE = YES; 463 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 464 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 465 | COPY_PHASE_STRIP = NO; 466 | CURRENT_PROJECT_VERSION = 1; 467 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 468 | ENABLE_NS_ASSERTIONS = NO; 469 | ENABLE_STRICT_OBJC_MSGSEND = YES; 470 | GCC_C_LANGUAGE_STANDARD = gnu99; 471 | GCC_NO_COMMON_BLOCKS = YES; 472 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 473 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 474 | GCC_WARN_UNDECLARED_SELECTOR = YES; 475 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 476 | GCC_WARN_UNUSED_FUNCTION = YES; 477 | GCC_WARN_UNUSED_VARIABLE = YES; 478 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 479 | MTL_ENABLE_DEBUG_INFO = NO; 480 | SDKROOT = iphoneos; 481 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 482 | TARGETED_DEVICE_FAMILY = "1,2"; 483 | VALIDATE_PRODUCT = YES; 484 | VERSIONING_SYSTEM = "apple-generic"; 485 | VERSION_INFO_PREFIX = ""; 486 | }; 487 | name = Release; 488 | }; 489 | FACE33C11EC9E779003EC6C0 /* Debug */ = { 490 | isa = XCBuildConfiguration; 491 | buildSettings = { 492 | APPLICATION_EXTENSION_API_ONLY = YES; 493 | CLANG_ENABLE_MODULES = YES; 494 | CODE_SIGN_IDENTITY = ""; 495 | DEFINES_MODULE = YES; 496 | DEVELOPMENT_TEAM = W7AMYM5LPN; 497 | DYLIB_COMPATIBILITY_VERSION = 1; 498 | DYLIB_CURRENT_VERSION = 1; 499 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 500 | INFOPLIST_FILE = PGPFormat/Info.plist; 501 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 502 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 503 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 504 | PRODUCT_BUNDLE_IDENTIFIER = co.krypt.PGPFormat; 505 | PRODUCT_NAME = "$(TARGET_NAME)"; 506 | SKIP_INSTALL = YES; 507 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 508 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 509 | SWIFT_VERSION = 5.0; 510 | }; 511 | name = Debug; 512 | }; 513 | FACE33C21EC9E779003EC6C0 /* Release */ = { 514 | isa = XCBuildConfiguration; 515 | buildSettings = { 516 | APPLICATION_EXTENSION_API_ONLY = YES; 517 | CLANG_ENABLE_MODULES = YES; 518 | CODE_SIGN_IDENTITY = ""; 519 | DEFINES_MODULE = YES; 520 | DEVELOPMENT_TEAM = W7AMYM5LPN; 521 | DYLIB_COMPATIBILITY_VERSION = 1; 522 | DYLIB_CURRENT_VERSION = 1; 523 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 524 | INFOPLIST_FILE = PGPFormat/Info.plist; 525 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 526 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 527 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 528 | PRODUCT_BUNDLE_IDENTIFIER = co.krypt.PGPFormat; 529 | PRODUCT_NAME = "$(TARGET_NAME)"; 530 | SKIP_INSTALL = YES; 531 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 532 | SWIFT_VERSION = 5.0; 533 | }; 534 | name = Release; 535 | }; 536 | FAEA705F1ECC192D00920AAB /* Debug */ = { 537 | isa = XCBuildConfiguration; 538 | buildSettings = { 539 | DEVELOPMENT_TEAM = W7AMYM5LPN; 540 | INFOPLIST_FILE = PGPFormatTests/Info.plist; 541 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 542 | PRODUCT_BUNDLE_IDENTIFIER = co.krypt.PGPFormatTests; 543 | PRODUCT_NAME = "$(TARGET_NAME)"; 544 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 545 | SWIFT_VERSION = 5.0; 546 | }; 547 | name = Debug; 548 | }; 549 | FAEA70601ECC192D00920AAB /* Release */ = { 550 | isa = XCBuildConfiguration; 551 | buildSettings = { 552 | DEVELOPMENT_TEAM = W7AMYM5LPN; 553 | INFOPLIST_FILE = PGPFormatTests/Info.plist; 554 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 555 | PRODUCT_BUNDLE_IDENTIFIER = co.krypt.PGPFormatTests; 556 | PRODUCT_NAME = "$(TARGET_NAME)"; 557 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 558 | SWIFT_VERSION = 5.0; 559 | }; 560 | name = Release; 561 | }; 562 | /* End XCBuildConfiguration section */ 563 | 564 | /* Begin XCConfigurationList section */ 565 | FACE33B21EC9E779003EC6C0 /* Build configuration list for PBXProject "PGPFormat" */ = { 566 | isa = XCConfigurationList; 567 | buildConfigurations = ( 568 | FACE33BE1EC9E779003EC6C0 /* Debug */, 569 | FACE33BF1EC9E779003EC6C0 /* Release */, 570 | ); 571 | defaultConfigurationIsVisible = 0; 572 | defaultConfigurationName = Release; 573 | }; 574 | FACE33C01EC9E779003EC6C0 /* Build configuration list for PBXNativeTarget "PGPFormat" */ = { 575 | isa = XCConfigurationList; 576 | buildConfigurations = ( 577 | FACE33C11EC9E779003EC6C0 /* Debug */, 578 | FACE33C21EC9E779003EC6C0 /* Release */, 579 | ); 580 | defaultConfigurationIsVisible = 0; 581 | defaultConfigurationName = Release; 582 | }; 583 | FAEA705E1ECC192D00920AAB /* Build configuration list for PBXNativeTarget "PGPFormatTests" */ = { 584 | isa = XCConfigurationList; 585 | buildConfigurations = ( 586 | FAEA705F1ECC192D00920AAB /* Debug */, 587 | FAEA70601ECC192D00920AAB /* Release */, 588 | ); 589 | defaultConfigurationIsVisible = 0; 590 | defaultConfigurationName = Release; 591 | }; 592 | /* End XCConfigurationList section */ 593 | }; 594 | rootObject = FACE33AF1EC9E779003EC6C0 /* Project object */; 595 | } 596 | -------------------------------------------------------------------------------- /PGPFormat.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PGPFormat/AsciiArmor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsciiArmor.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/15/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | ASCII Armor block constants 14 | */ 15 | public enum ArmorMessageBlock:String { 16 | case publicKey = "PUBLIC KEY BLOCK" 17 | case signature = "SIGNATURE" 18 | case message = "MESSAGE" 19 | 20 | var begin:String { 21 | return "\(ArmorMessageBlock.begin)\(self.rawValue)\(ArmorMessageBlock.dashes)" 22 | } 23 | 24 | var end:String { 25 | return "\(ArmorMessageBlock.end)\(self.rawValue)\(ArmorMessageBlock.dashes)" 26 | } 27 | 28 | static let dashes = "-----" 29 | static let begin = "-----BEGIN PGP " 30 | static let end = "-----END PGP " 31 | 32 | static var commentPrefix:String { 33 | return "Comment:" 34 | } 35 | 36 | 37 | init?(line:String) { 38 | let strippedHeader = line.replacingOccurrences(of: ArmorMessageBlock.begin, with: "").replacingOccurrences(of: ArmorMessageBlock.end, with: "").replacingOccurrences(of: ArmorMessageBlock.dashes, with: "") 39 | 40 | self.init(rawValue: strippedHeader) 41 | } 42 | 43 | } 44 | 45 | /** 46 | ASCII Armor Parsing Errors 47 | */ 48 | public enum AsciiArmorError:Error { 49 | case noValidHeader 50 | case blockLineMismatch 51 | case missingChecksum 52 | case invalidChecksum 53 | case invalidArmor 54 | 55 | } 56 | 57 | /** 58 | An ASCII Armored PGP Message. 59 | For example: 60 | 61 | -----BEGIN PGP PUBLIC KEY BLOCK----- 62 | Comment: 63 | Data 64 | "=" + CRC24(Data) 65 | -----END PGP PUBLIC KEY BLOCK----- 66 | 67 | https://tools.ietf.org/html/rfc4880#section-6.2 68 | */ 69 | public struct AsciiArmorMessage { 70 | 71 | public let packetData:Data 72 | public let crcChecksum:Data 73 | public let blockType:ArmorMessageBlock 74 | public var comment:String? 75 | 76 | 77 | public init(packetData:Data, blockType:ArmorMessageBlock, comment:String?) { 78 | self.packetData = packetData 79 | self.crcChecksum = packetData.crc24Checksum 80 | self.blockType = blockType 81 | self.comment = comment 82 | } 83 | 84 | /** 85 | Convert a PGP Message to an ASCII Armored PGP Message block 86 | */ 87 | public init(message:Message, blockType:ArmorMessageBlock, comment:String? = Constants.defaultASCIIArmorComment) { 88 | self.init(packetData: message.data(), blockType: blockType, comment: comment) 89 | } 90 | 91 | /** 92 | Parse an ASCII Armor string 93 | */ 94 | public init(string:String) throws { 95 | let lines = string.components(separatedBy: CharacterSet.newlines).filter { !$0.isEmpty } 96 | 97 | guard lines.count > 0, 98 | let headerBlockType = ArmorMessageBlock(line: lines[0].trimmingCharacters(in: CharacterSet.whitespaces)) 99 | else { 100 | throw AsciiArmorError.noValidHeader 101 | } 102 | 103 | guard lines.count > 3 else { 104 | throw AsciiArmorError.invalidArmor 105 | } 106 | 107 | var packetStart = 1 108 | if lines[1].hasPrefix(ArmorMessageBlock.commentPrefix) 109 | { 110 | self.comment = lines[1].replacingOccurrences(of: ArmorMessageBlock.commentPrefix, with: "").trimmingCharacters(in: CharacterSet.whitespaces) 111 | packetStart += 1 112 | } 113 | 114 | // crc 115 | self.crcChecksum = try lines[lines.count - 2].replacingOccurrences(of: "=", with: "").fromBase64() 116 | 117 | // footer 118 | let footerBlockType = ArmorMessageBlock(line: lines[lines.count - 1].trimmingCharacters(in: CharacterSet.whitespaces)) 119 | 120 | guard headerBlockType == footerBlockType else { 121 | throw AsciiArmorError.blockLineMismatch 122 | } 123 | 124 | self.blockType = headerBlockType 125 | 126 | let packets = try lines[packetStart ..< (lines.count - 2)].joined(separator: "").fromBase64() 127 | 128 | guard self.crcChecksum == packets.crc24Checksum else { 129 | throw AsciiArmorError.invalidChecksum 130 | } 131 | 132 | self.packetData = packets 133 | } 134 | 135 | /** 136 | Returns the ascii armored representation 137 | */ 138 | public func toString() -> String { 139 | let packetDataB64 = packetData.base64EncodedString(options: [.lineLength64Characters, .endLineWithLineFeed]) 140 | 141 | var result = "" 142 | 143 | result += "\(blockType.begin)\n" 144 | 145 | if let comment = self.comment { 146 | result += "\(ArmorMessageBlock.commentPrefix) \(comment)\n" 147 | } 148 | result += "\n" 149 | result += "\(packetDataB64)\n" 150 | result += "=\(crcChecksum.toBase64())\n" 151 | result += "\(blockType.end)" 152 | 153 | return result 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /PGPFormat/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /PGPFormat/LiteralData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LiteralData.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 6/20/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A Literal Data packet 13 | https://tools.ietf.org/html/rfc4880#section-5.9 14 | */ 15 | public struct LiteralData:Packetable { 16 | 17 | public var tag:PacketTag { 18 | return .literalData 19 | } 20 | 21 | public enum ParsingError:Error { 22 | case unsupportedFormatType(UInt8) 23 | } 24 | 25 | public enum SerializingError:Error { 26 | case filenameTooLong(Int) 27 | } 28 | 29 | 30 | public enum FormatType:UInt8 { 31 | case binary = 0x62 32 | case text = 0x74 33 | case utf8Text = 0x75 34 | 35 | init(type:UInt8) throws { 36 | guard let formatType = FormatType(rawValue: type) else { 37 | throw ParsingError.unsupportedFormatType(type) 38 | } 39 | self = formatType 40 | } 41 | } 42 | 43 | public var formatType:FormatType 44 | public var filename:Data 45 | public var date:Date 46 | public var contents:Data 47 | 48 | public init(packet:Packet) throws { 49 | guard packet.header.tag == .literalData else { 50 | throw PacketableError.invalidPacketTag(packet.header.tag) 51 | } 52 | 53 | // parse the body 54 | let data = packet.body 55 | 56 | guard data.count >= 5 else { 57 | throw DataError.tooShort(data.count) 58 | } 59 | 60 | let bytes = data.bytes 61 | 62 | // format 63 | formatType = try FormatType(type: bytes[0]) 64 | 65 | // filename (1 byte length, then string) 66 | let filenameLength = Int(bytes[1]) 67 | 68 | var ptr = 2 69 | guard data.count >= ptr + filenameLength else { 70 | throw DataError.tooShort(data.count) 71 | 72 | } 73 | filename = Data(bytes[ptr ..< (ptr + filenameLength)]) 74 | ptr += filenameLength 75 | 76 | // date (1 ..< 5) 77 | guard data.count >= ptr + 4 else { 78 | throw DataError.tooShort(data.count) 79 | } 80 | 81 | let creationSeconds = Double(UInt32(bigEndianBytes: [UInt8](bytes[ptr ..< (ptr + 4)]))) 82 | date = Date(timeIntervalSince1970: creationSeconds) 83 | ptr += 4 84 | 85 | // contents, remainder 86 | guard data.count >= ptr+1 else { 87 | throw DataError.tooShort(data.count) 88 | } 89 | 90 | contents = Data([UInt8](bytes.suffix(from: ptr))) 91 | } 92 | 93 | init(contents:Data, formatType:FormatType = .binary, filename:Data = Data(), date:Date = Date()) { 94 | self.contents = contents 95 | self.formatType = formatType 96 | self.filename = filename 97 | self.date = date 98 | } 99 | 100 | public func toData() throws -> Data { 101 | var data = Data() 102 | 103 | // format byte 104 | data.append(contentsOf: [formatType.rawValue]) 105 | 106 | // filename length 1 byte, plus contents 107 | guard filename.count < Int(UInt8.max) else { 108 | throw SerializingError.filenameTooLong(filename.count) 109 | } 110 | data.append(UInt8(filename.count)) 111 | data.append(filename) 112 | 113 | // date 114 | data.append(contentsOf: UInt32(date.timeIntervalSince1970).fourByteBigEndianBytes()) 115 | 116 | // literal data 117 | data.append(contents) 118 | 119 | return data 120 | } 121 | } 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /PGPFormat/Messagable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Messagable.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 6/3/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Represents something that can be made into a PGP Message. 13 | */ 14 | public protocol Messagable { 15 | func toPackets() throws -> [Packet] 16 | } 17 | 18 | extension Messagable { 19 | public func toMessage() throws -> Message { 20 | return try Message(packets: self.toPackets()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PGPFormat/Message.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Message.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/29/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A PGP Message is a sequence of Packets. 13 | */ 14 | public struct Message { 15 | let packets:[Packet] 16 | 17 | public func data() -> Data { 18 | var packetData = Data() 19 | packets.forEach { 20 | packetData.append($0.toData()) 21 | } 22 | 23 | return packetData 24 | } 25 | 26 | public init (packets:[Packet]) { 27 | self.packets = packets 28 | } 29 | 30 | public init(data:Data) throws { 31 | try self.init(packets: [Packet](data: data)) 32 | } 33 | 34 | public init(base64:String) throws { 35 | try self.init(data: base64.fromBase64()) 36 | } 37 | 38 | public func armoredMessage(blockType:ArmorMessageBlock, comment:String? = nil) -> AsciiArmorMessage { 39 | return AsciiArmorMessage(message: self, blockType: blockType, comment: comment) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /PGPFormat/OnePassSignature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnePassSignature.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 6/20/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | A One Pass Signature packet 14 | https://tools.ietf.org/html/rfc4880#section-5.4 15 | */ 16 | public struct OnePassSignature:Packetable { 17 | 18 | public var tag:PacketTag { 19 | return .onePassSignature 20 | } 21 | 22 | public enum ParsingError:Error { 23 | case unsupportedVersion(UInt8) 24 | case unsupportedNestedFlag(UInt8) 25 | } 26 | 27 | public let supportedVersion = 3 28 | public var kind:Signature.Kind 29 | public var hashAlgorithm:Signature.HashAlgorithm 30 | public var publicKeyAlgorithm:PublicKeyAlgorithm 31 | public var keyID:Data 32 | public let nestedFlag:UInt8 = 1 33 | 34 | public init(packet:Packet) throws { 35 | guard packet.header.tag == .onePassSignature else { 36 | throw PacketableError.invalidPacketTag(packet.header.tag) 37 | } 38 | 39 | let data = packet.body 40 | 41 | guard data.count >= 4 else { 42 | throw DataError.tooShort(data.count) 43 | } 44 | 45 | let bytes = data.bytes 46 | 47 | guard Int(bytes[0]) == supportedVersion else { 48 | throw ParsingError.unsupportedVersion(bytes[0]) 49 | } 50 | 51 | kind = try Signature.Kind(type: bytes[1]) 52 | hashAlgorithm = try Signature.HashAlgorithm(type: bytes[2]) 53 | publicKeyAlgorithm = try PublicKeyAlgorithm(type: bytes[3]) 54 | 55 | // the byte pointer 56 | var ptr = 4 57 | 58 | // 8-byte key id 59 | guard data.count >= ptr + 8 else { 60 | throw DataError.tooShort(data.count) 61 | } 62 | 63 | keyID = Data(bytes[ptr ..< ptr + 8]) 64 | ptr += 8 65 | 66 | guard data.count >= ptr + 1 else { 67 | throw DataError.tooShort(data.count) 68 | } 69 | 70 | let nested = bytes[ptr] 71 | guard nested == nestedFlag else { 72 | throw ParsingError.unsupportedNestedFlag(nested) 73 | } 74 | } 75 | 76 | init(signature:Signature, keyID:Data) { 77 | self.kind = signature.kind 78 | self.hashAlgorithm = signature.hashAlgorithm 79 | self.publicKeyAlgorithm = signature.publicKeyAlgorithm 80 | self.keyID = keyID 81 | } 82 | 83 | public func toData() throws -> Data { 84 | var data = Data() 85 | 86 | data.append(contentsOf: [UInt8(supportedVersion)]) 87 | data.append(contentsOf: [kind.rawValue]) 88 | data.append(contentsOf: [hashAlgorithm.rawValue]) 89 | data.append(contentsOf: [publicKeyAlgorithm.rawValue]) 90 | data.append(contentsOf: keyID.bytes) 91 | data.append(contentsOf: [nestedFlag]) 92 | 93 | return data 94 | } 95 | 96 | } 97 | 98 | 99 | -------------------------------------------------------------------------------- /PGPFormat/PGPFormat.h: -------------------------------------------------------------------------------- 1 | // 2 | // PGPFormat.h 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/15/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for PGPFormat. 12 | FOUNDATION_EXPORT double PGPFormatVersionNumber; 13 | 14 | //! Project version string for PGPFormat. 15 | FOUNDATION_EXPORT const unsigned char PGPFormatVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | 21 | // CRC-24 Checksum 22 | // https://tools.ietf.org/html/rfc4880 23 | // section 6.1 24 | #define CRC24_INIT 0xB704CEL 25 | #define CRC24_POLY 0x1864CFBL 26 | 27 | typedef long crc24; 28 | crc24 crc_octets(unsigned char *octets, size_t len) 29 | { 30 | crc24 crc = CRC24_INIT; 31 | int i; 32 | while (len--) { 33 | crc ^= (*octets++) << 16; 34 | for (i = 0; i < 8; i++) { 35 | crc <<= 1; 36 | if (crc & 0x1000000) 37 | crc ^= CRC24_POLY; 38 | } 39 | } 40 | return crc & 0xFFFFFFL; 41 | } 42 | -------------------------------------------------------------------------------- /PGPFormat/PGPFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PGPFormat.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/15/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct Constants { 12 | public static let defaultASCIIArmorComment = "Created with swift-pgp" 13 | } 14 | -------------------------------------------------------------------------------- /PGPFormat/Packet.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Packet.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/15/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | A PGP data record. 14 | https://tools.ietf.org/html/rfc4880#section-4 15 | */ 16 | public struct Packet { 17 | public let header:PacketHeader 18 | public let body:Data 19 | 20 | public var length:Int { 21 | return header.realLength + body.count 22 | } 23 | 24 | public func toData() -> Data { 25 | var data = Data() 26 | 27 | data.append(contentsOf: header.bytes()) 28 | data.append(contentsOf: body) 29 | 30 | return data 31 | } 32 | } 33 | 34 | /** 35 | A list of PGP data records. 36 | */ 37 | public extension Array where Element == Packet { 38 | 39 | /** 40 | Initialize a list of packets from a byte sequence 41 | */ 42 | init(data:Data) throws { 43 | 44 | var packetStart = 0 45 | var packets:[Packet] = [] 46 | 47 | while packetStart < data.count { 48 | let nextData = Data(data.suffix(from: packetStart)) 49 | 50 | let header = try PacketHeader(data: nextData) 51 | let body = try nextData.subdata(in: header.bodyRange()) 52 | 53 | let packet = Packet(header: header, body: body) 54 | packets.append(packet) 55 | 56 | packetStart += packet.length 57 | } 58 | 59 | self = packets 60 | } 61 | } 62 | 63 | /** 64 | Packet creation/serialization errors 65 | */ 66 | public enum PacketError:Error { 67 | case msbUnset 68 | case unsupportedTagType(UInt8) 69 | 70 | case unsupportedNewFormatLengthType(UInt8) 71 | case unsupportedOldFormatLengthType(UInt8) 72 | 73 | case partial(UInt8) 74 | 75 | case bodyLengthTooLong(Int) 76 | case invalidPacketLengthFormatByteLength(Int) 77 | } 78 | 79 | 80 | /** 81 | A header for the packet to determine the packet tag 82 | identifier and body length 83 | 84 | First octet of the packet header: 85 | +---------------+ 86 | PTag |7 6 5 4 3 2 1 0| 87 | +---------------+ 88 | 89 | Bits 7-6: 90 | - Bit 7 -- Always one 91 | - Bit 6 -- New packet format if set 92 | 93 | Bits 5-0: 94 | - NewFormat: 95 | Bits 5-0 -- packet tag 96 | - OldFormat: 97 | Bits 5-2 -- packet tag 98 | Bits 1-0 -- length-type 99 | 100 | https://tools.ietf.org/html/rfc4880#section-4.2 101 | */ 102 | public struct PacketHeader { 103 | 104 | public let tag:PacketTag 105 | public let length:PacketLength 106 | 107 | private let tagLength = 1 108 | 109 | public var realLength:Int { 110 | return tagLength + length.byteLength() 111 | } 112 | 113 | /** 114 | Data range for packet body 115 | */ 116 | public func bodyRange() throws -> Range { 117 | let start = realLength 118 | let end = start + length.body 119 | 120 | guard start < end else { 121 | throw DataError.range(start, end) 122 | } 123 | 124 | return start ..< end 125 | } 126 | 127 | init(tag:PacketTag, packetLength:PacketLength) { 128 | self.length = packetLength 129 | self.tag = tag 130 | } 131 | 132 | /** 133 | Initialize packet header from byte sequence 134 | */ 135 | public init(data:Data) throws { 136 | guard data.count > 0 else { 137 | throw DataError.tooShort(data.count) 138 | } 139 | 140 | let bytes = data.bytes 141 | 142 | // parse packet tag 143 | let firstOctet = bytes[0] 144 | 145 | guard (firstOctet & 0b10000000) >> 7 == 1 else { 146 | throw PacketError.msbUnset 147 | } 148 | 149 | let newFormat = ((firstOctet & 0b01000000) >> 6) == 1 150 | 151 | if newFormat { 152 | let packetTag = try PacketTag(tag: firstOctet & 0b00111111) 153 | let packetLength = try PacketLength(newFormat: [UInt8](bytes.suffix(from: 1))) 154 | self.init(tag: packetTag, packetLength: packetLength) 155 | 156 | } else { 157 | let packetTag = try PacketTag(tag: (firstOctet & 0b00111100)>>2) 158 | let lengthType = firstOctet & 0b00000011 159 | let packetLength = try PacketLength(oldFormat: [UInt8](bytes.suffix(from: 1)), type: lengthType) 160 | 161 | self.init(tag: packetTag, packetLength: packetLength) 162 | } 163 | } 164 | 165 | /** 166 | Compute the first byte, the tag byte, of the packet header 167 | */ 168 | func tagByte() -> UInt8 { 169 | let msb:UInt8 = 0b10000000 170 | let format:UInt8 = length.isNewFormat() ? 0b01000000 : 0b00000000 171 | 172 | var tagBits:UInt8 173 | var lengthType:UInt8 174 | 175 | switch length.length { 176 | case .new(_): 177 | tagBits = tag.rawValue 178 | lengthType = 0b00000000 179 | case .old(let l): 180 | tagBits = tag.rawValue << 2 181 | lengthType = l.rawValue 182 | } 183 | 184 | return msb | format | tagBits | lengthType 185 | } 186 | 187 | /** 188 | Convert the packet header to a byte sequence 189 | */ 190 | func bytes() -> Data { 191 | var data = Data() 192 | 193 | data.append(contentsOf: [tagByte()]) 194 | data.append(contentsOf: length.formatBytes) 195 | 196 | return data 197 | } 198 | 199 | } 200 | 201 | /** 202 | Represents the type of packet (the packet tag) 203 | https://tools.ietf.org/html/rfc4880#section-4.3 204 | 205 | //NOTE: not all currently supported 206 | */ 207 | public enum PacketTag:UInt8 { 208 | case signature = 2 209 | case onePassSignature = 4 210 | case publicKey = 6 211 | case literalData = 11 212 | case userID = 13 213 | case publicSubkey = 14 214 | 215 | init(tag:UInt8) throws { 216 | guard let packetTag = PacketTag(rawValue: tag) else { 217 | throw PacketError.unsupportedTagType(tag) 218 | } 219 | self = packetTag 220 | } 221 | } 222 | 223 | 224 | /** 225 | Represents the length of the packet body 226 | */ 227 | public struct PacketLength { 228 | 229 | let length:Length 230 | let body:Int 231 | 232 | let formatBytes:[UInt8] 233 | 234 | /** 235 | Create a packet length from the length of a packet body 236 | //TODO: add support new format 237 | */ 238 | public init(body:Int) throws { 239 | self.body = body 240 | 241 | switch body { 242 | case 0 ..< Int(UInt8.max): 243 | length = .old(.oneOctet) 244 | formatBytes = [UInt8(body)] 245 | 246 | case 256 ..< Int(UInt16.max): 247 | length = .old(.twoOctet) 248 | formatBytes = UInt32(body).twoByteBigEndianBytes() 249 | 250 | case Int(UInt16.max) ..< Int(Int32.max): 251 | length = .old(.fourOctet) 252 | formatBytes = UInt32(body).fourByteBigEndianBytes() 253 | 254 | default: 255 | throw PacketError.bodyLengthTooLong(body) 256 | } 257 | } 258 | 259 | /** 260 | Initialize a packet length with from a 'New Format' packet header 261 | https://tools.ietf.org/html/rfc4880#section-4.2.2 262 | */ 263 | init(newFormat bytes:[UInt8]) throws { 264 | guard bytes.count > 0 else { 265 | throw DataError.tooShort(bytes.count) 266 | } 267 | 268 | switch Int(bytes[0]) { 269 | case 0...191: // one octet 270 | length = .new(.oneOctet) 271 | body = Int(bytes[0]) 272 | formatBytes = [bytes[0]] 273 | 274 | case 192 ..< 224 where bytes.count >= 2: // two octet 275 | let firstOctet = Int(bytes[0]) 276 | let secondOctet = Int(bytes[1]) 277 | 278 | length = .new(.twoOctet) 279 | body = ((firstOctet - 192) << 8) + secondOctet + 192 280 | formatBytes = [UInt8](bytes[0...1]) 281 | 282 | case 255 where bytes.count >= 5: // five octet 283 | let secondOctet = Int(bytes[1]) 284 | let thirdOctet = Int(bytes[2]) 285 | let fourthOctet = Int(bytes[3]) 286 | let fifthOctet = Int(bytes[4]) 287 | 288 | length = .new(.fiveOctet) 289 | body = (secondOctet << 24) | (thirdOctet << 16) | (fourthOctet << 8) | fifthOctet 290 | formatBytes = [UInt8](bytes[1...4]) 291 | 292 | default: 293 | throw PacketError.unsupportedNewFormatLengthType(bytes[0]) 294 | } 295 | } 296 | 297 | 298 | /** 299 | Initialize a packet length with from a 'Old Format' packet header 300 | https://tools.ietf.org/html/rfc4880#section-4.2.1 301 | */ 302 | init(oldFormat bytes:[UInt8], type:UInt8) throws { 303 | guard let lengthType = OldFormatType(rawValue: type) else { 304 | throw PacketError.unsupportedOldFormatLengthType(type) 305 | } 306 | 307 | switch lengthType { 308 | case .oneOctet where bytes.count >= 1: 309 | length = .old(.oneOctet) 310 | body = Int(bytes[0]) 311 | formatBytes = [bytes[0]] 312 | 313 | 314 | case .twoOctet where bytes.count >= 2: 315 | let firstOctet = Int(bytes[0]) 316 | let secondOctet = Int(bytes[1]) 317 | 318 | length = .old(.twoOctet) 319 | body = (firstOctet << 8) | secondOctet 320 | formatBytes = [UInt8](bytes[0...1]) 321 | 322 | 323 | case .fourOctet where bytes.count >= 4: 324 | let firstOctet = Int(bytes[0]) 325 | let secondOctet = Int(bytes[1]) 326 | let thirdOctet = Int(bytes[2]) 327 | let fourthOctet = Int(bytes[3]) 328 | 329 | length = .old(.fourOctet) 330 | body = (firstOctet << 24) | (secondOctet << 16) | (thirdOctet << 8) | fourthOctet 331 | formatBytes = [UInt8](bytes[0...3]) 332 | 333 | default: 334 | throw DataError.tooShort(bytes.count) 335 | } 336 | } 337 | 338 | enum OldFormatType:UInt8 { 339 | case oneOctet = 0 340 | case twoOctet = 1 341 | case fourOctet = 2 342 | } 343 | 344 | enum NewFormatType { 345 | case oneOctet 346 | case twoOctet 347 | case fiveOctet 348 | } 349 | 350 | enum Length { 351 | case new(NewFormatType) 352 | case old(OldFormatType) 353 | 354 | func byteLength() -> UInt8 { 355 | switch self { 356 | case .old(let l): 357 | switch l { 358 | case .oneOctet: 359 | return 1 360 | case .twoOctet: 361 | return 2 362 | case .fourOctet: 363 | return 4 364 | } 365 | case .new(let l): 366 | switch l { 367 | case .oneOctet: 368 | return 1 369 | case .twoOctet: 370 | return 2 371 | case .fiveOctet: 372 | return 5 373 | } 374 | } 375 | } 376 | } 377 | 378 | func byteLength() -> Int { 379 | return Int(length.byteLength()) 380 | } 381 | 382 | func isNewFormat() -> Bool { 383 | switch length { 384 | case .old(_): 385 | return false 386 | case .new(_): 387 | return true 388 | } 389 | } 390 | 391 | } 392 | 393 | 394 | -------------------------------------------------------------------------------- /PGPFormat/Packetable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Packetable.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 6/3/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Represents a structure that can be initialized 13 | from a packet and converted to a packet 14 | */ 15 | public protocol Packetable { 16 | /** 17 | Retreive the packet tag identiifer 18 | */ 19 | var tag:PacketTag { get } 20 | 21 | /** 22 | Create a Packetable from a packet 23 | */ 24 | init(packet:Packet) throws 25 | 26 | /** 27 | Convert a packetable to a byte sequence 28 | */ 29 | func toData() throws -> Data 30 | } 31 | 32 | public extension Packetable { 33 | 34 | /** 35 | Convert a packetable to a packet 36 | */ 37 | func toPacket() throws -> Packet { 38 | let body = try self.toData() 39 | let header = try PacketHeader(tag: self.tag, packetLength: PacketLength(body: body.count)) 40 | 41 | return Packet(header: header, body: body) 42 | } 43 | } 44 | 45 | /** 46 | Packetable initialization errors 47 | */ 48 | public enum PacketableError:Error { 49 | case invalidPacketTag(PacketTag) 50 | } 51 | 52 | /** 53 | Initialize a list of packetables from a byte sequence 54 | */ 55 | public extension Array where Element == Packetable { 56 | init(data:Data) throws { 57 | let packets = try [Packet](data: data) 58 | 59 | self = try packets.map { 60 | switch $0.header.tag { 61 | case .publicKey, .publicSubkey: 62 | return try PublicKey(packet: $0) 63 | case .userID: 64 | return try UserID(packet: $0) 65 | case .signature: 66 | return try Signature(packet: $0) 67 | case .onePassSignature: 68 | return try OnePassSignature(packet: $0) 69 | case .literalData: 70 | return try LiteralData(packet: $0) 71 | } 72 | } 73 | } 74 | } 75 | 76 | /** 77 | for testing purposes 78 | public struct BasePacket:Packetable { 79 | public var tag:PacketTag 80 | var bodyData:Data 81 | 82 | public init(packet:Packet) throws { 83 | tag = packet.header.tag 84 | bodyData = packet.body 85 | } 86 | 87 | public func toData() throws -> Data { 88 | return bodyData 89 | } 90 | } 91 | */ 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /PGPFormat/PublicKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublicKey.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/15/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | 10 | import Foundation 11 | 12 | /** 13 | Supported Public Key Algorithm Types 14 | Full list: https://tools.ietf.org/html/rfc4880#section-9.1 15 | + Ed25519: https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-00 16 | + ECDSA 17 | */ 18 | public enum PublicKeyAlgorithm:UInt8 { 19 | case rsaEncryptOrSign = 1 20 | case rsaEncryptOnly = 2 21 | case rsaSignOnly = 3 22 | case ecdsa = 19 23 | case ed25519 = 22 24 | 25 | init(type:UInt8) throws { 26 | guard let algo = PublicKeyAlgorithm(rawValue: type) else { 27 | throw UnsupportedType(type: type) 28 | } 29 | 30 | self = algo 31 | } 32 | 33 | public struct UnsupportedType:Error { 34 | var type:UInt8 35 | } 36 | } 37 | 38 | 39 | /** 40 | A Public Key packet 41 | */ 42 | public struct PublicKey:Packetable { 43 | 44 | /** 45 | Only supports version 4 46 | */ 47 | private let supportedVersion = 4 48 | 49 | public let tag:PacketTag 50 | 51 | public var created:Date 52 | public var algorithm:PublicKeyAlgorithm 53 | public var publicKeyData:PublicKeyData 54 | 55 | /** 56 | Public key packet parsing error 57 | */ 58 | public enum ParsingError:Error { 59 | case tooShort(Int) 60 | case unsupportedVersion(UInt8) 61 | case invalidFinerprintLength(Int) 62 | } 63 | 64 | public init(create algorithm:PublicKeyAlgorithm, publicKeyData:PublicKeyData, date:Date = Date()) { 65 | self.tag = .publicKey 66 | self.algorithm = algorithm 67 | self.publicKeyData = publicKeyData 68 | self.created = date 69 | } 70 | 71 | /** 72 | Initialize a Public Key from a packet 73 | */ 74 | public init(packet:Packet) throws { 75 | 76 | // get packet tag, ensure it's a public key type 77 | switch packet.header.tag { 78 | case .publicKey, .publicSubkey: 79 | self.tag = packet.header.tag 80 | default: 81 | throw PacketableError.invalidPacketTag(packet.header.tag) 82 | } 83 | 84 | // parse the body 85 | let data = packet.body 86 | 87 | guard data.count > 6 else { 88 | throw DataError.tooShort(data.count) 89 | } 90 | 91 | let bytes = data.bytes 92 | 93 | // version (0) 94 | guard Int(bytes[0]) == supportedVersion else { 95 | throw ParsingError.unsupportedVersion(bytes[0]) 96 | } 97 | 98 | // created (1 ..< 5) 99 | let creationSeconds = Double(UInt32(bigEndianBytes: [UInt8](bytes[1 ..< 5]))) 100 | created = Date(timeIntervalSince1970: creationSeconds) 101 | 102 | // algo (5) 103 | algorithm = try PublicKeyAlgorithm(type: bytes[5]) 104 | 105 | // parse the key data 106 | let keyData = Data(bytes[6 ..< bytes.count]) 107 | 108 | switch algorithm { 109 | case .rsaSignOnly, .rsaEncryptOnly, .rsaEncryptOrSign: 110 | self.publicKeyData = try RSAPublicKey(mpintData: keyData) 111 | 112 | case .ed25519, .ecdsa: 113 | self.publicKeyData = try ECPublicKey(mpintData: keyData) 114 | } 115 | } 116 | 117 | /** 118 | Serialize public key to packet body data 119 | */ 120 | public func toData() -> Data { 121 | 122 | var data = Data() 123 | 124 | // add supported version 125 | data.append(contentsOf: [UInt8(supportedVersion)]) 126 | 127 | // add created time 128 | data.append(contentsOf: UInt32(created.timeIntervalSince1970).fourByteBigEndianBytes()) 129 | 130 | // add algorithm 131 | data.append(contentsOf: [algorithm.rawValue]) 132 | 133 | // add public key data 134 | data.append(publicKeyData.toData()) 135 | 136 | return data 137 | } 138 | 139 | /** 140 | Compute the SHA1 fingerprint of the public key 141 | https://tools.ietf.org/html/rfc4880#section-12.2 142 | */ 143 | public func fingerprint() -> Data { 144 | var dataToHash = Data() 145 | dataToHash.append(contentsOf: [0x99]) 146 | 147 | // pubkey length + data 148 | let publicKeyPacketData = self.toData() 149 | let pubKeyLengthBytes = UInt32(publicKeyPacketData.count).twoByteBigEndianBytes() 150 | dataToHash.append(contentsOf: pubKeyLengthBytes) 151 | dataToHash.append(publicKeyPacketData) 152 | 153 | return dataToHash.SHA1 154 | } 155 | 156 | /** 157 | Compute the KeyID of a public key from its fingerprint 158 | */ 159 | public func keyID() throws -> Data { 160 | let fingerprint = self.fingerprint() 161 | 162 | guard fingerprint.count == 20 else { 163 | throw ParsingError.invalidFinerprintLength(fingerprint.count) 164 | } 165 | 166 | return Data(fingerprint.bytes[12 ..< 20]) 167 | } 168 | } 169 | 170 | 171 | -------------------------------------------------------------------------------- /PGPFormat/PublicKeyData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublicKeyData.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 6/3/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Represents a public key data structure 13 | */ 14 | public protocol PublicKeyData { 15 | init(mpintData:Data) throws 16 | func toData() -> Data 17 | } 18 | 19 | /** 20 | The RSA public key data structure 21 | */ 22 | public struct RSAPublicKey:PublicKeyData{ 23 | public let modulus:MPInt 24 | public let exponent:MPInt 25 | 26 | public init(modulus:Data, exponent:Data) { 27 | self.modulus = MPInt(integerData: modulus) 28 | self.exponent = MPInt(integerData: exponent) 29 | } 30 | 31 | public init(mpintData: Data) throws { 32 | let bytes = mpintData.bytes 33 | 34 | var start = 0 35 | 36 | self.modulus = try MPInt(mpintData: Data(bytes[start ..< bytes.count])) 37 | start += modulus.byteLength 38 | 39 | guard bytes.count >= start else { 40 | throw DataError.tooShort(bytes.count) 41 | } 42 | 43 | self.exponent = try MPInt(mpintData: Data(bytes[start ..< bytes.count])) 44 | } 45 | 46 | public func toData() -> Data { 47 | var data = Data() 48 | 49 | // modulus: MPI two-octet scalar length then modulus 50 | data.append(contentsOf: modulus.lengthBytes) 51 | data.append(modulus.data) 52 | 53 | // exponent: MPI two-octet scalar length then exponent 54 | data.append(contentsOf: exponent.lengthBytes) 55 | data.append(exponent.data) 56 | 57 | return data 58 | } 59 | } 60 | 61 | /** 62 | The ECC public key data structure 63 | - supports Ed25519: https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-00 64 | - supports ECDSA (nistP256): https://tools.ietf.org/html/rfc6637 65 | */ 66 | 67 | public struct ECPublicKey:PublicKeyData { 68 | 69 | let curve:Curve 70 | let rawData:Data 71 | 72 | enum ParsingError:Error { 73 | case invalidOrMissingECCPrefixByte 74 | case badECCCurveOIDLength(UInt8) 75 | case unsupportedECCCurveOID([UInt8]) 76 | } 77 | 78 | public enum Curve { 79 | case ed25519 80 | case nistP256 81 | 82 | static var supported:[Curve] { 83 | return [.ed25519, .nistP256] 84 | } 85 | 86 | var prefixByte:UInt8 { 87 | switch self { 88 | case .ed25519: 89 | return 0x40 90 | case .nistP256: 91 | return 0x04 92 | } 93 | } 94 | 95 | var oid:[UInt8] { 96 | switch self { 97 | case .ed25519: 98 | return [0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01] 99 | case .nistP256: 100 | return [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07] 101 | } 102 | } 103 | 104 | init?(oid:[UInt8]) { 105 | for curve in Curve.supported { 106 | if curve.oid == oid { 107 | self = curve 108 | return 109 | } 110 | } 111 | 112 | return nil 113 | } 114 | } 115 | 116 | public init(curve:Curve, rawData:Data) { 117 | self.curve = curve 118 | self.rawData = rawData 119 | } 120 | 121 | public init(curve:Curve, prefixedRawData:Data) throws { 122 | guard prefixedRawData.count >= 1 else { 123 | throw DataError.tooShort(prefixedRawData.count) 124 | } 125 | 126 | guard curve.prefixByte == prefixedRawData[0] else { 127 | throw ParsingError.invalidOrMissingECCPrefixByte 128 | } 129 | 130 | self.curve = curve 131 | self.rawData = Data(prefixedRawData.suffix(from: 1)) 132 | } 133 | 134 | public init(mpintData:Data) throws { 135 | 136 | let bytes = mpintData.bytes 137 | 138 | guard bytes.count >= 1 else { 139 | throw DataError.tooShort(bytes.count) 140 | } 141 | 142 | var start = 0 143 | let oidLength = Int(bytes[start]) 144 | 145 | guard bytes.count >= 1 + oidLength else { 146 | throw DataError.tooShort(bytes.count) 147 | } 148 | 149 | start += 1 150 | 151 | let oid = [UInt8](bytes[start ..< start + oidLength]) 152 | 153 | guard let curve = Curve(oid: oid) else { 154 | throw ParsingError.unsupportedECCCurveOID(oid) 155 | } 156 | 157 | self.curve = curve 158 | 159 | start += oidLength 160 | 161 | guard bytes.count > start else { 162 | throw DataError.tooShort(bytes.count) 163 | } 164 | 165 | let mpintBytes = try MPInt(mpintData: Data(bytes[start ..< bytes.count])).data.bytes 166 | 167 | guard mpintBytes.first == curve.prefixByte else { 168 | throw ParsingError.invalidOrMissingECCPrefixByte 169 | } 170 | 171 | guard mpintBytes.count > 1 else { 172 | throw DataError.tooShort(mpintBytes.count) 173 | } 174 | 175 | self.rawData = Data(mpintBytes[1 ..< mpintBytes.count]) 176 | } 177 | 178 | 179 | public func toData() -> Data { 180 | var data = Data() 181 | data.append(contentsOf: [UInt8(curve.oid.count)] + curve.oid) 182 | 183 | let mpint = MPInt(integerData: Data([curve.prefixByte] + rawData.bytes)) 184 | 185 | data.append(contentsOf: mpint.lengthBytes) 186 | data.append(mpint.data) 187 | 188 | return data 189 | } 190 | } 191 | 192 | -------------------------------------------------------------------------------- /PGPFormat/Signable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Signable.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/21/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Represents a structure that can be signed 13 | */ 14 | public protocol Signable { 15 | var signature:Signature { get set } 16 | func signableData() throws -> Data 17 | } 18 | 19 | public extension Signable { 20 | func dataToHash() throws -> Data { 21 | var dataToHash = try self.signableData() 22 | try dataToHash.append(signature.dataToHash()) 23 | 24 | return dataToHash 25 | } 26 | 27 | mutating func set(hash:Data, signedHash:[Data]) throws { 28 | try signature.set(hash: hash, signedHash: signedHash) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /PGPFormat/Signature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Signature.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/15/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | 10 | import Foundation 11 | 12 | 13 | /** 14 | A Signature packet 15 | https://tools.ietf.org/html/rfc4880#section-5.2 16 | */ 17 | public struct Signature:Packetable { 18 | 19 | public var tag:PacketTag { 20 | return .signature 21 | } 22 | 23 | /** 24 | The type of signature 25 | https://tools.ietf.org/html/rfc4880#section-5.2.1 26 | */ 27 | public enum Kind:UInt8 { 28 | case binaryDocument = 0x00 29 | case userID = 0x10 30 | case personalUserID = 0x11 31 | case casualUserID = 0x12 32 | case positiveUserID = 0x13 33 | case subKey = 0x18 34 | case primaryKey = 0x19 35 | 36 | init(type:UInt8) throws { 37 | guard let sigType = Kind(rawValue: type) else { 38 | throw ParsingError.unsupportedSignatureType(type) 39 | } 40 | self = sigType 41 | } 42 | } 43 | 44 | /** 45 | Signature Hash Algorithm 46 | https://tools.ietf.org/html/rfc4880#section-9.4 47 | */ 48 | public enum HashAlgorithm:UInt8 { 49 | case sha1 = 2 50 | case sha256 = 8 51 | case sha384 = 9 52 | case sha512 = 10 53 | case sha224 = 11 54 | 55 | init(type:UInt8) throws { 56 | guard let algo = HashAlgorithm(rawValue: type) else { 57 | throw ParsingError.unsupportedHashAlgorithm(type) 58 | } 59 | 60 | self = algo 61 | } 62 | } 63 | 64 | /** 65 | Signature error types 66 | */ 67 | public enum ParsingError:Error { 68 | case unsupportedSignatureType(UInt8) 69 | case unsupportedVersion(UInt8) 70 | case unsupportedHashAlgorithm(UInt8) 71 | case signatureHasExtraBytes(Int) 72 | } 73 | 74 | public enum SerializingError:Error { 75 | case tooManySubpackets 76 | case signatureTooShort 77 | case invalidSignatureLength(Int) 78 | case invalidHashLength(Int) 79 | } 80 | 81 | 82 | 83 | /** 84 | Only support version 4 signatures 85 | */ 86 | public let supportedVersion = 4 87 | 88 | public var kind:Kind 89 | public var publicKeyAlgorithm:PublicKeyAlgorithm 90 | public var hashAlgorithm:HashAlgorithm 91 | public var hashedSubpacketables:[SignatureSubpacketable] 92 | public var unhashedSubpacketables:[SignatureSubpacketable] 93 | public var signature:[Data] 94 | public var leftTwoHashBytes:[UInt8] 95 | 96 | /** 97 | Initialize a signature from a packet 98 | */ 99 | public init(packet:Packet) throws { 100 | guard packet.header.tag == .signature else { 101 | throw PacketableError.invalidPacketTag(packet.header.tag) 102 | } 103 | 104 | let data = packet.body 105 | 106 | guard data.count >= 6 else { 107 | throw DataError.tooShort(data.count) 108 | } 109 | 110 | let bytes = data.bytes 111 | 112 | guard Int(bytes[0]) == supportedVersion else { 113 | throw ParsingError.unsupportedVersion(bytes[0]) 114 | } 115 | 116 | kind = try Kind(type: bytes[1]) 117 | publicKeyAlgorithm = try PublicKeyAlgorithm(type: bytes[2]) 118 | hashAlgorithm = try HashAlgorithm(type: bytes[3]) 119 | 120 | 121 | // hashed subpackets 122 | let hashedDataLength = Int(UInt32(bigEndianBytes: [UInt8](bytes[4 ... 5]))) 123 | 124 | var ptr = 6 125 | guard bytes.count >= ptr + hashedDataLength else { 126 | throw DataError.tooShort(bytes.count) 127 | } 128 | 129 | hashedSubpacketables = try [SignatureSubpacket](data: Data(bytes[ptr ..< (ptr + hashedDataLength)])).toSignatureSubpacketables() 130 | 131 | ptr += hashedDataLength 132 | 133 | // unhashed subpackets 134 | guard bytes.count >= ptr + 2 else { 135 | throw DataError.tooShort(bytes.count) 136 | } 137 | 138 | let unhashedDataLength = Int(UInt32(bigEndianBytes: [UInt8](bytes[ptr ... ptr + 1]))) 139 | ptr += 2 140 | 141 | guard bytes.count >= ptr + unhashedDataLength else { 142 | throw DataError.tooShort(bytes.count) 143 | } 144 | 145 | unhashedSubpacketables = try [SignatureSubpacket](data: Data(bytes[ptr ..< (ptr + unhashedDataLength)])).toSignatureSubpacketables() 146 | ptr += unhashedDataLength 147 | 148 | 149 | // left 16 bits of signed hash 150 | guard bytes.count >= ptr + 2 else { 151 | throw DataError.tooShort(bytes.count) 152 | } 153 | 154 | // ignoring 155 | leftTwoHashBytes = [UInt8](bytes[ptr ... (ptr + 1)]) 156 | 157 | ptr += 2 // jump two-octets for left 16 bits of sig 158 | 159 | guard bytes.count > ptr else { 160 | throw DataError.tooShort(bytes.count) 161 | } 162 | 163 | // signature MPI 164 | switch publicKeyAlgorithm { 165 | case .rsaEncryptOrSign, .rsaSignOnly: 166 | let signatureMPInt = try MPInt(mpintData: Data(bytes[ptr ..< bytes.count])) 167 | signature = [signatureMPInt.data] 168 | ptr += signatureMPInt.byteLength 169 | 170 | case .ed25519: 171 | // first point 172 | let firstPointMPInt = try MPInt(mpintData: Data(bytes[ptr ..< bytes.count])) 173 | 174 | ptr += firstPointMPInt.byteLength 175 | 176 | // second point 177 | guard bytes.count > ptr else { 178 | throw DataError.tooShort(bytes.count) 179 | } 180 | 181 | let secondPointMPint = try MPInt(mpintData: Data(bytes[ptr ..< bytes.count])) 182 | 183 | ptr += secondPointMPint.byteLength 184 | 185 | // pad points with 0s if needed 186 | let firstPoint = firstPointMPInt.data.padPrependedZeros(upto: 32) 187 | let secondPoint = secondPointMPint.data.padPrependedZeros(upto: 32) 188 | 189 | // concat points for raw signature data 190 | signature = [firstPoint, secondPoint] 191 | 192 | case .ecdsa: 193 | // first point 194 | let firstPointMPInt = try MPInt(mpintData: Data(bytes[ptr ..< bytes.count])) 195 | 196 | ptr += firstPointMPInt.byteLength 197 | 198 | // second point 199 | guard bytes.count > ptr else { 200 | throw DataError.tooShort(bytes.count) 201 | } 202 | 203 | let secondPointMPint = try MPInt(mpintData: Data(bytes[ptr ..< bytes.count])) 204 | 205 | ptr += secondPointMPint.byteLength 206 | 207 | // concat points for raw signature data 208 | signature = [firstPointMPInt.data, secondPointMPint.data] 209 | 210 | case .rsaEncryptOnly: 211 | throw PublicKeyAlgorithm.UnsupportedType(type: publicKeyAlgorithm.rawValue) 212 | 213 | } 214 | 215 | guard bytes.count == ptr else { 216 | throw ParsingError.signatureHasExtraBytes(bytes.count - ptr) 217 | } 218 | 219 | } 220 | 221 | // MARK: Signing Helpers 222 | public init(bare kind:Kind, publicKeyAlgorithm:PublicKeyAlgorithm, hashAlgorithm:HashAlgorithm, hashedSubpacketables:[SignatureSubpacketable] = []) { 223 | self.kind = kind 224 | self.publicKeyAlgorithm = publicKeyAlgorithm 225 | self.hashAlgorithm = hashAlgorithm 226 | self.hashedSubpacketables = hashedSubpacketables 227 | self.unhashedSubpacketables = [] 228 | self.leftTwoHashBytes = [] 229 | self.signature = [] 230 | } 231 | 232 | /** 233 | Serialize the signature data that is part of the data to hash and sign 234 | https://tools.ietf.org/html/rfc4880#section-5.2.4 235 | */ 236 | public func signedData() throws -> Data { 237 | var data = Data() 238 | 239 | data.append(contentsOf: [UInt8(supportedVersion)]) 240 | data.append(contentsOf: [kind.rawValue]) 241 | data.append(contentsOf: [publicKeyAlgorithm.rawValue]) 242 | data.append(contentsOf: [hashAlgorithm.rawValue]) 243 | 244 | // hashed subpackets 245 | let hashedSubpackets = try hashedSubpacketables.map({ try $0.toSubpacket() }) 246 | let hashedSubpacketLength = hashedSubpackets.reduce(0, { $0 + $1.length }) 247 | guard hashedSubpacketLength <= Int(UInt16.max) else { 248 | throw SerializingError.tooManySubpackets 249 | } 250 | // length 251 | data.append(contentsOf: UInt32(hashedSubpacketLength).twoByteBigEndianBytes()) 252 | // data 253 | hashedSubpackets.forEach { 254 | data.append($0.toData()) 255 | } 256 | 257 | return data 258 | } 259 | 260 | /** 261 | Serialize the signedData with the trailer that is to be hashed 262 | https://tools.ietf.org/html/rfc4880#section-5.2.4 263 | */ 264 | public func dataToHash() throws -> Data { 265 | var dataToHash = Data() 266 | 267 | // append signature data 268 | let signatureData = try self.signedData() 269 | dataToHash.append(signatureData) 270 | 271 | // trailer 272 | dataToHash.append(self.trailer(for: signatureData)) 273 | 274 | return dataToHash 275 | } 276 | 277 | /** 278 | Signature trailer 279 | https://tools.ietf.org/html/rfc4880#section-5.2.4 280 | */ 281 | public func trailer(for signatureData:Data) -> Data { 282 | // trailer 283 | var data = Data() 284 | data.append(contentsOf: [UInt8(supportedVersion)]) 285 | data.append(contentsOf: [0xFF]) 286 | data.append(contentsOf: UInt32(signatureData.count).fourByteBigEndianBytes()) 287 | 288 | return data 289 | } 290 | 291 | /** 292 | Set the signature data and left two hash bytes 293 | */ 294 | mutating public func set(hash:Data, signedHash:[Data]) throws { 295 | guard hash.count >= 2 else { 296 | throw Signature.SerializingError.invalidHashLength(hash.count) 297 | } 298 | self.leftTwoHashBytes = [UInt8](hash.bytes[0...1]) 299 | self.signature = signedHash 300 | } 301 | 302 | /** 303 | Serialize signature to packet body 304 | */ 305 | public func toData() throws -> Data { 306 | var data = try signedData() 307 | 308 | // un-hashed subpackets 309 | let unhashedSubpackets = try unhashedSubpacketables.map({ try $0.toSubpacket() }) 310 | let unhashedSubpacketLength = unhashedSubpackets.reduce(0, { $0 + $1.length }) 311 | guard unhashedSubpacketLength <= Int(UInt16.max) else { 312 | throw SerializingError.tooManySubpackets 313 | } 314 | // length 315 | data.append(contentsOf: UInt32(unhashedSubpacketLength).twoByteBigEndianBytes()) 316 | // data 317 | unhashedSubpackets.forEach { 318 | data.append($0.toData()) 319 | } 320 | 321 | // left 16 bits 322 | data.append(contentsOf: leftTwoHashBytes) 323 | 324 | // signature MPI 325 | switch publicKeyAlgorithm { 326 | case .rsaEncryptOrSign, .rsaSignOnly, .ecdsa, .ed25519: 327 | for point in signature { 328 | let signatureMPInt = MPInt(integerData: point) 329 | 330 | data.append(contentsOf: signatureMPInt.lengthBytes) 331 | data.append(signatureMPInt.data) 332 | } 333 | 334 | case .rsaEncryptOnly: 335 | throw PublicKeyAlgorithm.UnsupportedType(type: publicKeyAlgorithm.rawValue) 336 | } 337 | 338 | return data 339 | } 340 | 341 | } 342 | 343 | 344 | -------------------------------------------------------------------------------- /PGPFormat/SignatureSubpacket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignatureSubpacket.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/18/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A Signature record type known as a "Signature Subpacket" 13 | https://tools.ietf.org/html/rfc4880#section-5.2.3.1 14 | */ 15 | public struct SignatureSubpacket { 16 | public let header:SignatureSubpacketHeader 17 | public let body:Data 18 | 19 | public var length:Int { 20 | return header.length + body.count 21 | } 22 | 23 | public func toData() -> Data { 24 | var data = Data() 25 | 26 | data.append(contentsOf: header.lengthBytes) 27 | data.append(contentsOf: [header.subpacketType.rawValue]) 28 | data.append(contentsOf: body) 29 | 30 | return data 31 | } 32 | } 33 | 34 | /** 35 | Signature subpacket error types 36 | */ 37 | public enum SignatureSubpacketError:Error { 38 | case invalidLength(Int) 39 | case unsupportedType(UInt8) 40 | } 41 | 42 | /** 43 | A list of signature subpackets 44 | */ 45 | public extension Array where Element == SignatureSubpacket { 46 | 47 | init(data:Data) throws { 48 | 49 | var packetStart = 0 50 | 51 | var packets:[SignatureSubpacket] = [] 52 | 53 | while packetStart < data.count { 54 | let nextData = Data(data.suffix(from: packetStart)) 55 | 56 | let header = try SignatureSubpacketHeader(data: nextData) 57 | let body = try nextData.safeSubdata(in: header.bodyRange()) 58 | let packet = SignatureSubpacket(header: header, body: body) 59 | 60 | packets.append(packet) 61 | 62 | packetStart += packet.length 63 | } 64 | 65 | self = packets 66 | } 67 | 68 | } 69 | 70 | 71 | /** 72 | The header representing the body length of a Signature Subpacket 73 | */ 74 | public struct SignatureSubpacketHeader { 75 | public var subpacketType:SignatureSubpacketType 76 | 77 | private let typeLength = 1 78 | 79 | public let lengthLength:Int 80 | public let lengthBytes:[UInt8] 81 | public let bodyLength:Int 82 | 83 | public var length:Int { 84 | return typeLength + lengthLength 85 | } 86 | 87 | public func bodyRange() throws -> Range { 88 | let start = typeLength + lengthLength 89 | let end = start + bodyLength - typeLength 90 | 91 | guard start < end else { 92 | throw DataError.range(start, end) 93 | } 94 | 95 | return start ..< end 96 | } 97 | 98 | init(type:SignatureSubpacketType, bodyLength:Int) throws { 99 | self.subpacketType = type 100 | self.bodyLength = bodyLength 101 | 102 | let realLength = bodyLength + typeLength 103 | 104 | switch realLength { 105 | case 0 ..< 192: 106 | self.lengthLength = 1 107 | self.lengthBytes = [UInt8(realLength)] 108 | 109 | case 192 ..< 8384: 110 | self.lengthLength = 2 111 | 112 | let firstByte = UInt8((UInt16(realLength - 192) >> 8) + 192) 113 | let secondByte = UInt8((realLength - 192) % Int(UInt8.max)) 114 | self.lengthBytes = [firstByte, secondByte] 115 | 116 | case 8384 ..< Int(Int32.max): 117 | self.lengthLength = 5 118 | self.lengthBytes = [UInt8(255)] + UInt32(realLength).fourByteBigEndianBytes() 119 | 120 | default: 121 | throw SignatureSubpacketError.invalidLength(realLength) 122 | } 123 | 124 | 125 | } 126 | 127 | /** 128 | Initialize a subpacket header from a byte sequence 129 | */ 130 | init(data:Data) throws { 131 | guard data.count > 0 else { 132 | throw DataError.tooShort(data.count) 133 | } 134 | 135 | let bytes = data.bytes 136 | 137 | // read the length 138 | switch Int(bytes[0]) { 139 | case 0...191: 140 | lengthLength = 1 141 | bodyLength = Int(bytes[0]) 142 | lengthBytes = [bytes[0]] 143 | 144 | case 192 ..< 224 where bytes.count > 1: 145 | let firstOctet = Int(bytes[0]) 146 | let secondOctet = Int(bytes[1]) 147 | lengthBytes = [UInt8](bytes[0...1]) 148 | 149 | lengthLength = 2 150 | bodyLength = ((firstOctet - 192) << 8) + secondOctet + 192 151 | 152 | case 255 where bytes.count > 4: 153 | let secondOctet = Int(bytes[1]) 154 | let thirdOctet = Int(bytes[2]) 155 | let fourthOctet = Int(bytes[3]) 156 | let fifthOctet = Int(bytes[4]) 157 | 158 | lengthLength = 5 159 | bodyLength = (secondOctet << 24) | (thirdOctet << 16) | (fourthOctet << 8) | fifthOctet 160 | lengthBytes = [UInt8](bytes[1...4]) 161 | 162 | default: 163 | throw SignatureSubpacketError.invalidLength(bytes.count) 164 | } 165 | 166 | // read the type 167 | guard bytes.count >= lengthLength + typeLength else { 168 | throw DataError.tooShort(bytes.count) 169 | } 170 | 171 | subpacketType = try SignatureSubpacketType(type: bytes[lengthLength + typeLength - 1]) 172 | } 173 | 174 | } 175 | 176 | -------------------------------------------------------------------------------- /PGPFormat/SignatureSubpacketTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignatureSubpacketTypes.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/19/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /** 13 | Represents possible Signature Subpacket types 14 | https://tools.ietf.org/html/rfc4880#section-5.2.3.1 15 | */ 16 | public enum SignatureSubpacketType:UInt8 { 17 | case created = 2 18 | case keyExpires = 9 19 | case issuer = 16 20 | case keyFlags = 27 21 | 22 | // not handeled specifically 23 | case sigExpires = 3 24 | case trust = 5 25 | case preferedSymmetricKeyAlgorithms = 11 26 | case preferedHashAlgorithms = 21 27 | case preferedCompressionAlgorithms = 22 28 | case keyServer = 23 29 | case preferedKeyServer = 24 30 | case primaryUserID = 25 31 | case features = 30 32 | 33 | case issuerFingerprint = 33 34 | 35 | init(type:UInt8) throws { 36 | guard let sigType = SignatureSubpacketType(rawValue: type) else { 37 | throw SignatureSubpacketError.unsupportedType(type) 38 | } 39 | 40 | self = sigType 41 | } 42 | 43 | } 44 | 45 | /** 46 | Signature Creation Time 47 | https://tools.ietf.org/html/rfc4880#section-5.2.3.4 48 | */ 49 | public struct SignatureCreated:SignatureSubpacketable { 50 | public var type:SignatureSubpacketType { 51 | return .created 52 | } 53 | 54 | public var date:Date 55 | 56 | public enum SignatureCreatedError:Error { 57 | case invalidBodyLength(Int) 58 | } 59 | 60 | public init(date:Date) { 61 | self.date = date 62 | } 63 | 64 | public init(packet:SignatureSubpacket) throws { 65 | guard packet.header.subpacketType == .created else { 66 | throw SignatureSubpacketableError.invalidSubpacketType(packet.header.subpacketType) 67 | } 68 | 69 | guard packet.body.count == 4 else { 70 | throw SignatureCreatedError.invalidBodyLength(packet.body.count) 71 | } 72 | 73 | 74 | let creationSeconds = Double(UInt32(bigEndianBytes: [UInt8](packet.body.bytes[0 ... 3]))) 75 | date = Date(timeIntervalSince1970: creationSeconds) 76 | } 77 | 78 | public func toData() throws -> Data { 79 | return Data(UInt32(date.timeIntervalSince1970).fourByteBigEndianBytes()) 80 | } 81 | } 82 | 83 | /** 84 | Signature Issuer 85 | https://tools.ietf.org/html/rfc4880#section-5.2.3.5 86 | */ 87 | public struct SignatureIssuer:SignatureSubpacketable, CustomDebugStringConvertible { 88 | public var type:SignatureSubpacketType { 89 | return .issuer 90 | } 91 | 92 | public var keyID:Data 93 | 94 | public enum SignatureIssuer:Error { 95 | case invalidBodyLength(Int) 96 | } 97 | 98 | public init(keyID:Data) { 99 | self.keyID = keyID 100 | } 101 | 102 | public init(packet:SignatureSubpacket) throws { 103 | guard packet.header.subpacketType == .issuer else { 104 | throw SignatureSubpacketableError.invalidSubpacketType(packet.header.subpacketType) 105 | } 106 | 107 | guard packet.body.count == 8 else { 108 | throw SignatureIssuer.invalidBodyLength(packet.body.count) 109 | } 110 | 111 | keyID = Data(packet.body.subdata(in: 0 ..< 8)) 112 | } 113 | 114 | public func toData() throws -> Data { 115 | return keyID 116 | } 117 | 118 | 119 | public var debugDescription:String { 120 | return keyID.hex.uppercased() 121 | } 122 | } 123 | 124 | /** 125 | Signature Key Expiration Time 126 | https://tools.ietf.org/html/rfc4880#section-5.2.3.6 127 | */ 128 | public struct SignatureKeyExpires:SignatureSubpacketable { 129 | public var type:SignatureSubpacketType { 130 | return .keyExpires 131 | } 132 | 133 | public var date:Date 134 | 135 | public enum SignatureKeyExpires:Error { 136 | case invalidBodyLength(Int) 137 | } 138 | 139 | public init(packet:SignatureSubpacket) throws { 140 | guard packet.header.subpacketType == .keyExpires else { 141 | throw SignatureSubpacketableError.invalidSubpacketType(packet.header.subpacketType) 142 | } 143 | 144 | guard packet.body.count == 4 else { 145 | throw SignatureKeyExpires.invalidBodyLength(packet.body.count) 146 | } 147 | 148 | 149 | let expirationSeconds = Double(UInt32(bigEndianBytes: [UInt8](packet.body.bytes[0 ... 3]))) 150 | date = Date(timeIntervalSince1970: expirationSeconds) 151 | } 152 | 153 | public func toData() throws -> Data { 154 | return Data(UInt32(date.timeIntervalSince1970).fourByteBigEndianBytes()) 155 | } 156 | } 157 | 158 | /** 159 | Signature Issuer Fingerprint 160 | */ 161 | public struct SignatureIssuerFingerprint:SignatureSubpacketable { 162 | public var type:SignatureSubpacketType { 163 | return .issuerFingerprint 164 | } 165 | 166 | public var fingerprint:Data 167 | 168 | public init(fingerprint:Data) { 169 | self.fingerprint = fingerprint 170 | } 171 | 172 | public init(packet:SignatureSubpacket) throws { 173 | guard packet.header.subpacketType == .issuerFingerprint else { 174 | throw SignatureSubpacketableError.invalidSubpacketType(packet.header.subpacketType) 175 | } 176 | 177 | fingerprint = packet.body 178 | } 179 | 180 | public func toData() throws -> Data { 181 | return fingerprint 182 | } 183 | } 184 | 185 | /** 186 | Signature Key Flags 187 | https://tools.ietf.org/html/rfc4880#section-5.2.3.21 188 | */ 189 | public struct SignatureKeyFlags:SignatureSubpacketable, CustomDebugStringConvertible { 190 | public var type:SignatureSubpacketType { 191 | return .keyFlags 192 | } 193 | 194 | public enum FlagType:UInt8 { 195 | case certifyOtherKeys = 0x01 196 | case signData = 0x02 197 | case encryptCommunication = 0x04 198 | case encryptStorage = 0x08 199 | case splitKey = 0x10 200 | case authentication = 0x20 201 | case ownedBySeveral = 0x80 202 | } 203 | 204 | public var flags:[FlagType] 205 | public var unknowns:[UInt8] 206 | 207 | public enum SignatureIssuer:Error { 208 | case invalidBodyLength(Int) 209 | } 210 | 211 | public init(flagTypes:[FlagType]) { 212 | flags = flagTypes 213 | unknowns = [] 214 | } 215 | 216 | public init(packet:SignatureSubpacket) throws { 217 | guard packet.header.subpacketType == .keyFlags else { 218 | throw SignatureSubpacketableError.invalidSubpacketType(packet.header.subpacketType) 219 | } 220 | 221 | flags = [] 222 | unknowns = [] 223 | 224 | for byte in packet.body.bytes { 225 | guard let flag = FlagType(rawValue: byte) else { 226 | unknowns.append(byte) 227 | continue 228 | } 229 | 230 | flags.append(flag) 231 | } 232 | } 233 | 234 | public func toData() throws -> Data { 235 | var data = Data(flags.map({ $0.rawValue })) 236 | data.append(contentsOf: unknowns) 237 | 238 | return data 239 | } 240 | 241 | public var debugDescription:String { 242 | return "Key Flags: \(flags)" 243 | } 244 | } 245 | 246 | /** 247 | A default signature subpacket 248 | */ 249 | public struct SignatureUnparsedSubpacket:SignatureSubpacketable { 250 | public var type:SignatureSubpacketType 251 | public var body:Data 252 | 253 | public init(packet:SignatureSubpacket) throws { 254 | self.type = packet.header.subpacketType 255 | self.body = packet.body 256 | } 257 | 258 | public func toData() throws -> Data { 259 | return body 260 | } 261 | } 262 | 263 | -------------------------------------------------------------------------------- /PGPFormat/SignatureSubpacketable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignatureSubpacketable.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 6/4/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Represents a structure that can be initialized 13 | from a SignatureSubpacket and converted to a SignatureSubpacket 14 | */ 15 | public protocol SignatureSubpacketable { 16 | var type:SignatureSubpacketType { get } 17 | init(packet:SignatureSubpacket) throws 18 | func toData() throws -> Data 19 | } 20 | 21 | public extension SignatureSubpacketable { 22 | func toSubpacket() throws -> SignatureSubpacket { 23 | let body = try self.toData() 24 | let header = try SignatureSubpacketHeader(type: self.type, bodyLength: body.count) 25 | 26 | return SignatureSubpacket(header: header, body: body) 27 | } 28 | } 29 | 30 | public enum SignatureSubpacketableError:Error { 31 | case invalidSubpacketType(SignatureSubpacketType) 32 | } 33 | 34 | 35 | /** 36 | Convert a list of signature subpackets to a list 37 | of signature subpacketables 38 | */ 39 | public extension Array where Element == SignatureSubpacket { 40 | func toSignatureSubpacketables() throws -> [SignatureSubpacketable] { 41 | var subpacketables = [SignatureSubpacketable]() 42 | 43 | for packet in self { 44 | switch packet.header.subpacketType { 45 | case .created: 46 | try subpacketables.append(SignatureCreated(packet: packet)) 47 | case .keyExpires: 48 | try subpacketables.append(SignatureKeyExpires(packet: packet)) 49 | case .issuer: 50 | try subpacketables.append(SignatureIssuer(packet: packet)) 51 | case .keyFlags: 52 | try subpacketables.append(SignatureKeyFlags(packet: packet)) 53 | case .issuerFingerprint: 54 | try subpacketables.append(SignatureIssuerFingerprint(packet: packet)) 55 | default: 56 | try subpacketables.append(SignatureUnparsedSubpacket(packet: packet)) 57 | } 58 | } 59 | 60 | return subpacketables 61 | } 62 | 63 | } 64 | 65 | 66 | -------------------------------------------------------------------------------- /PGPFormat/SignedAttachedBinaryDocument.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignedAttachedBinaryDocument.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 6/20/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Represents a signed binary document with the attached binary document as 13 | a literal data packet 14 | Packets: one pass signature packet, literal data packet, a signature packet 15 | */ 16 | public struct SignedAttachedBinaryDocument:Signable, Messagable { 17 | public var literalData:LiteralData 18 | public var keyID:Data 19 | public var signature:Signature 20 | 21 | public init(binaryData:Data, binaryDate:Date = Date(), keyID:Data, publicKeyAlgorithm:PublicKeyAlgorithm, hashAlgorithm:Signature.HashAlgorithm, hashedSubpacketables:[SignatureSubpacketable]) { 22 | 23 | self.literalData = LiteralData(contents: binaryData, formatType: .binary, date: binaryDate) 24 | self.keyID = keyID 25 | 26 | signature = Signature(bare: Signature.Kind.binaryDocument, publicKeyAlgorithm: publicKeyAlgorithm, hashAlgorithm: hashAlgorithm, hashedSubpacketables: hashedSubpacketables) 27 | } 28 | 29 | public func signableData() throws -> Data { 30 | return literalData.contents 31 | } 32 | 33 | public func toPackets() throws -> [Packet] { 34 | // create the one pass signature 35 | let onePass = OnePassSignature(signature: signature, keyID: keyID) 36 | 37 | // return one pass, literal data, and the signature packets 38 | return try [onePass.toPacket(), literalData.toPacket(), signature.toPacket()] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PGPFormat/SignedBinaryDocument.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SignedBinaryDocument.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/21/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Represents a signed binary document 13 | Packets: a signature packet 14 | */ 15 | public struct SignedBinaryDocument:Signable, Messagable { 16 | public var binaryData:Data 17 | 18 | public var signature:Signature 19 | public init(binary:Data, publicKeyAlgorithm:PublicKeyAlgorithm, hashAlgorithm:Signature.HashAlgorithm, hashedSubpacketables:[SignatureSubpacketable]) { 20 | 21 | binaryData = binary 22 | signature = Signature(bare: Signature.Kind.binaryDocument, publicKeyAlgorithm: publicKeyAlgorithm, hashAlgorithm: hashAlgorithm, hashedSubpacketables: hashedSubpacketables) 23 | } 24 | 25 | public func signableData() throws -> Data { 26 | return binaryData 27 | } 28 | 29 | public func toPackets() throws -> [Packet] { 30 | return try [self.signature.toPacket()] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /PGPFormat/SignedPublicKeyIdentity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublicKeyToSign.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/19/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A signed public key identity 13 | A list of packets: public key, user id, signatures 14 | */ 15 | public struct SignedPublicKeyIdentity:Signable, Messagable { 16 | 17 | public let publicKey:PublicKey 18 | public let userID:UserID 19 | public var signature:Signature 20 | 21 | public init(publicKey:PublicKey, userID:UserID, hashAlgorithm:Signature.HashAlgorithm, hashedSubpacketables:[SignatureSubpacketable] = []) throws { 22 | self.publicKey = publicKey 23 | self.userID = userID 24 | 25 | self.signature = Signature(bare: Signature.Kind.positiveUserID, publicKeyAlgorithm: publicKey.algorithm, hashAlgorithm: hashAlgorithm, hashedSubpacketables: hashedSubpacketables) 26 | 27 | self.signature.unhashedSubpacketables = try [SignatureIssuer(keyID: publicKey.keyID())] 28 | 29 | } 30 | 31 | public func signableData() -> Data { 32 | var dataToHash = Data() 33 | dataToHash.append(contentsOf: [0x99]) 34 | 35 | // pubkey length + data 36 | let publicKeyPacketData = publicKey.toData() 37 | let pubKeyLengthBytes = UInt32(publicKeyPacketData.count).twoByteBigEndianBytes() 38 | dataToHash.append(contentsOf: pubKeyLengthBytes) 39 | dataToHash.append(publicKeyPacketData) 40 | 41 | // userid byte, length + data 42 | let userIdPacketData = userID.toData() 43 | let userIdLengthBytes = UInt32(userIdPacketData.count).fourByteBigEndianBytes() 44 | dataToHash.append(contentsOf: [0xB4]) 45 | dataToHash.append(contentsOf: userIdLengthBytes) 46 | dataToHash.append(userIdPacketData) 47 | 48 | return dataToHash 49 | } 50 | 51 | public func toPackets() throws -> [Packet] { 52 | return try [publicKey.toPacket(), userID.toPacket(), signature.toPacket()] 53 | } 54 | } 55 | 56 | /** 57 | A list of signed public key identites 58 | */ 59 | public struct SignedPublicKeyIdentities:Messagable { 60 | let signedPublicKeys:[SignedPublicKeyIdentity] 61 | 62 | public init(_ signedIdentities:[SignedPublicKeyIdentity]) { 63 | self.signedPublicKeys = signedIdentities 64 | } 65 | 66 | public func toPackets() throws -> [Packet] { 67 | var packets = [Packet]() 68 | 69 | if let first = signedPublicKeys.first { 70 | try packets.append(first.publicKey.toPacket()) 71 | } 72 | 73 | try signedPublicKeys.forEach { 74 | try packets.append(contentsOf: [$0.userID.toPacket(), $0.signature.toPacket()]) 75 | } 76 | 77 | return packets 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /PGPFormat/UserID.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserID.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/15/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | 10 | import Foundation 11 | 12 | /** 13 | A UserID packet 14 | https://tools.ietf.org/html/rfc4880#section-5.11 15 | */ 16 | public struct UserID:Packetable { 17 | 18 | public var tag:PacketTag { 19 | return .userID 20 | } 21 | 22 | public var name:String? 23 | public var email:String? 24 | 25 | public var content:String 26 | 27 | public init(packet:Packet) throws { 28 | guard packet.header.tag == .userID else { 29 | throw PacketableError.invalidPacketTag(packet.header.tag) 30 | } 31 | 32 | guard let all = String(data: packet.body, encoding: .utf8) 33 | else { 34 | throw DataError.encoding 35 | } 36 | 37 | self.init(content: all) 38 | } 39 | 40 | public init(name:String, email:String) { 41 | self.name = name 42 | self.email = email 43 | self.content = "\(name) <\(email)>" 44 | } 45 | 46 | public init(content:String) { 47 | self.content = content 48 | setNameAndEmail() 49 | } 50 | 51 | 52 | public func toData() -> Data { 53 | return Data([UInt8](content.utf8)) 54 | } 55 | 56 | mutating private func setNameAndEmail() { 57 | let components = content.components(separatedBy: "<") 58 | guard components.count >= 2 else { 59 | return 60 | } 61 | 62 | self.name = components[0].trimmingCharacters(in: CharacterSet.whitespaces) 63 | self.email = components[1].replacingOccurrences(of: ">", with: "").trimmingCharacters(in: CharacterSet.whitespaces) 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /PGPFormat/Util.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Util.swift 3 | // PGPFormat 4 | // 5 | // Created by Alex Grinman on 5/15/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | 10 | import Foundation 11 | import Security 12 | import CommonCrypto 13 | 14 | public enum DataError : Error { 15 | case encoding 16 | case cryptoRandom 17 | case fingerprint 18 | case tooShort(Int) 19 | case range(Int,Int) 20 | } 21 | 22 | public struct MPInt { 23 | 24 | public var data:Data 25 | 26 | /** 27 | Initialize an MPInt with integer bytes 28 | remove any leading zero bytes 29 | */ 30 | public init(integerData:Data) { 31 | let bytes = integerData.bytes 32 | 33 | var startingIndex = 0 34 | for byte in bytes { 35 | guard Int(byte) == 0 else { 36 | break 37 | } 38 | 39 | startingIndex += 1 40 | } 41 | 42 | self.data = Data(bytes[startingIndex ..< bytes.count]) 43 | } 44 | 45 | /** 46 | Initialize an MPInt with MPInt bytes 47 | */ 48 | public init(mpintData:Data) throws { 49 | guard mpintData.count >= 2 else { 50 | throw DataError.tooShort(mpintData.count) 51 | } 52 | 53 | let bytes = mpintData.bytes 54 | 55 | var ptr = 0 56 | 57 | let length = Int(UInt32(bigEndianBytes: [UInt8](bytes[ptr ... ptr + 1])) + 7)/8 58 | ptr += 2 59 | 60 | guard bytes.count >= ptr + length else { 61 | throw DataError.tooShort(bytes.count) 62 | } 63 | 64 | data = Data(bytes[ptr ..< (ptr + length)]) 65 | } 66 | 67 | public var byteLength:Int { 68 | return 2 + Int(UInt32(bigEndianBytes: lengthBytes) + 7)/8 69 | } 70 | 71 | public var lengthBytes:[UInt8] { 72 | return data.numBits.twoByteBigEndianBytes() 73 | } 74 | 75 | } 76 | extension Int { 77 | var numBits:Int { 78 | guard self > 0 else { 79 | return 0 80 | } 81 | 82 | return Int(floor(log2(Double(self)))) + 1 83 | } 84 | } 85 | 86 | extension Data { 87 | var SHA512:Data { 88 | var dataBytes = self.bytes 89 | var hash = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH)) 90 | CC_SHA512(&dataBytes, CC_LONG(self.count), &hash) 91 | 92 | return Data(hash) 93 | } 94 | var SHA384:Data { 95 | var dataBytes = self.bytes 96 | var hash = [UInt8](repeating: 0, count: Int(CC_SHA384_DIGEST_LENGTH)) 97 | CC_SHA384(&dataBytes, CC_LONG(self.count), &hash) 98 | 99 | return Data(hash) 100 | } 101 | 102 | var SHA256:Data { 103 | var dataBytes = self.bytes 104 | var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 105 | CC_SHA256(&dataBytes, CC_LONG(self.count), &hash) 106 | 107 | return Data(hash) 108 | } 109 | 110 | var SHA224:Data { 111 | var dataBytes = self.bytes 112 | var hash = [UInt8](repeating: 0, count: Int(CC_SHA224_DIGEST_LENGTH)) 113 | CC_SHA224(&dataBytes, CC_LONG(self.count), &hash) 114 | 115 | return Data(hash) 116 | } 117 | 118 | var SHA1:Data { 119 | var dataBytes = self.bytes 120 | var hash = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) 121 | CC_SHA1(&dataBytes, CC_LONG(self.count), &hash) 122 | 123 | return Data(hash) 124 | } 125 | } 126 | 127 | 128 | extension Data { 129 | 130 | var numBits:Int { 131 | guard count > 0 else { 132 | return 0 133 | } 134 | 135 | let dataBytes = self.bytes 136 | 137 | var byteIndex = 0 138 | for byte in dataBytes { 139 | guard Int(byte) == 0 else { 140 | break 141 | } 142 | 143 | byteIndex += 1 144 | } 145 | 146 | guard byteIndex < count else { 147 | return 0 148 | } 149 | 150 | let firstByteBits = Int(dataBytes[byteIndex]).numBits 151 | let remainingBytesBits = (count - byteIndex - 1)*8 152 | 153 | return firstByteBits + remainingBytesBits 154 | } 155 | 156 | internal var bytes:[UInt8] { 157 | return self.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in 158 | [UInt8](UnsafeRawBufferPointer(start: bytes.baseAddress, count: self.count)) 159 | } 160 | } 161 | 162 | var crc24Checksum:Data { 163 | var dataBytes = self.bytes 164 | let checksum = crc_octets(&dataBytes, dataBytes.count) 165 | 166 | guard checksum <= 0xFFFFFF else { 167 | return Data() 168 | } 169 | 170 | return Data(UInt32(checksum).threeByteBigEndianBytes()) 171 | } 172 | 173 | 174 | /** 175 | Create a new byte array with prepended zeros 176 | so that the final length is equal to `length`. 177 | 178 | If the length is greater than `length`, return itself. 179 | */ 180 | func padPrependedZeros(upto length:Int) -> Data { 181 | guard self.count < length else { 182 | return Data(self) 183 | } 184 | 185 | let zeros = Data(repeating: 0, count: length - self.count) 186 | 187 | var padded = Data() 188 | padded.append(zeros) 189 | padded.append(self) 190 | 191 | return padded 192 | } 193 | 194 | 195 | func toBase64(_ urlEncoded:Bool = false) -> String { 196 | var result = self.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) 197 | 198 | if urlEncoded { 199 | result = result.replacingOccurrences(of: "/", with: "_") 200 | result = result.replacingOccurrences(of: "+", with: "-") 201 | } 202 | 203 | return result 204 | } 205 | 206 | func byteArray() -> [String] { 207 | var array:[String] = [] 208 | 209 | for i in 0 ..< self.count { 210 | var byte: UInt8 = 0 211 | (self as NSData).getBytes(&byte, range: NSMakeRange(i, 1)) 212 | array.append(NSString(format: "%d", byte) as String) 213 | } 214 | 215 | return array 216 | } 217 | 218 | 219 | func safeSubdata(in range:Range) throws -> Data { 220 | guard self.count >= range.lowerBound + 1, 221 | self.count >= range.upperBound 222 | else { 223 | throw DataError.range(range.lowerBound, range.upperBound) 224 | } 225 | 226 | return self.subdata(in: range) 227 | } 228 | 229 | var hex:String { 230 | let bytes = self.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in 231 | [UInt8](UnsafeRawBufferPointer(start: bytes.baseAddress, count: self.count)) 232 | } 233 | 234 | var hexString = "" 235 | for i in 0.. [String] { 255 | var array:[String] = [] 256 | 257 | for i in 0 ..< self.length { 258 | var byte: UInt8 = 0 259 | self.getBytes(&byte, range: NSMakeRange(i, 1)) 260 | array.append(NSString(format: "%d", byte) as String) 261 | } 262 | 263 | return array 264 | } 265 | } 266 | 267 | extension String { 268 | func fromBase64() throws -> Data { 269 | var urlDecoded = self 270 | urlDecoded = urlDecoded.replacingOccurrences(of: "_", with: "/") 271 | urlDecoded = urlDecoded.replacingOccurrences(of: "-", with: "+") 272 | 273 | guard let data = Data(base64Encoded: urlDecoded, options: Data.Base64DecodingOptions.ignoreUnknownCharacters) else { 274 | throw DataError.encoding 275 | } 276 | 277 | return data 278 | } 279 | } 280 | 281 | extension Data { 282 | func bigEndianByteSize() -> [UInt8] { 283 | return stride(from: 24, through: 0, by: -8).map { 284 | UInt8(truncatingIfNeeded: UInt32(self.count).littleEndian >> UInt32($0)) 285 | } 286 | } 287 | } 288 | 289 | extension UInt32 { 290 | init(bigEndianBytes: [UInt8]) { 291 | let count = UInt32(bigEndianBytes.count) 292 | 293 | var val : UInt32 = 0 294 | for i in UInt32(0) ..< count { 295 | val += UInt32(bigEndianBytes[Int(i)]) << ((count - 1 - i) * 8) 296 | } 297 | self.init(val) 298 | } 299 | 300 | func fourByteBigEndianBytes() -> [UInt8] { 301 | return [UInt8((self >> 24) % 256), UInt8((self >> 16) % 256), UInt8((self >> 8) % 256), UInt8((self) % 256)] 302 | } 303 | 304 | func threeByteBigEndianBytes() -> [UInt8] { 305 | return [UInt8((self >> 16) % 256), UInt8((self >> 8) % 256), UInt8((self) % 256)] 306 | } 307 | 308 | func twoByteBigEndianBytes() -> [UInt8] { 309 | return [UInt8((self >> 8) % 256), UInt8((self) % 256)] 310 | } 311 | } 312 | 313 | extension Int { 314 | func twoByteBigEndianBytes() -> [UInt8] { 315 | return [UInt8((self >> 8) % 256), UInt8((self) % 256)] 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /PGPFormatTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PGPFormatTests/PGPFormatTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PGPFormatTests.swift 3 | // PGPFormatTests 4 | // 5 | // Created by Alex Grinman on 5/17/17. 6 | // Copyright © 2017 KryptCo, Inc. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import PGPFormat 11 | import CommonCrypto 12 | 13 | class PGPFormatTests: XCTestCase { 14 | 15 | var pubkey1:String! 16 | var pubkey2:String! 17 | var pubkeyEd25519:String! 18 | var pubkeyNISTP256:String! 19 | 20 | var binarySignature:String! 21 | var binaryDocument:String! 22 | 23 | var attachedSignature:String! 24 | 25 | 26 | override func setUp() { 27 | super.setUp() 28 | 29 | let bundle = Bundle(for: type(of: self)) 30 | pubkey1 = try! String(contentsOfFile: bundle.path(forResource: "pubkey1", ofType: "txt")!) 31 | pubkey2 = try! String(contentsOfFile: bundle.path(forResource: "pubkey2", ofType: "txt")!) 32 | pubkeyEd25519 = try! String(contentsOfFile: bundle.path(forResource: "pubkey3", ofType: "txt")!) 33 | pubkeyNISTP256 = try! String(contentsOfFile: bundle.path(forResource: "pubkey4", ofType: "txt")!) 34 | 35 | binarySignature = try! String(contentsOfFile: bundle.path(forResource: "signature", ofType: "txt")!) 36 | binaryDocument = try! String(contentsOfFile: bundle.path(forResource: "signed_raw", ofType: "txt")!) 37 | 38 | attachedSignature = try! String(contentsOfFile: bundle.path(forResource: "attached_signature", ofType: "txt")!) 39 | 40 | } 41 | 42 | override func tearDown() { 43 | super.tearDown() 44 | } 45 | 46 | //MARK: Ascii Armor 47 | func testAsciiArmor() { 48 | do { 49 | let pubMsg = try AsciiArmorMessage(string: pubkey1) 50 | 51 | if pubMsg.comment != "Some test hello" { 52 | XCTFail("invalid comment: \(String(describing: pubMsg.comment))") 53 | } 54 | 55 | if pubMsg.crcChecksum.toBase64() != "m4zw" { 56 | XCTFail("invalid checksum: \(pubMsg.crcChecksum.toBase64())") 57 | } 58 | 59 | if pubMsg.blockType != ArmorMessageBlock.publicKey { 60 | XCTFail("invalid block type: \(pubMsg.blockType)") 61 | } 62 | 63 | let _ = try AsciiArmorMessage(string: pubMsg.toString()) 64 | 65 | } catch { 66 | XCTFail("Unexpected error: \(error)") 67 | } 68 | } 69 | 70 | func testChecksum() { 71 | let data = try! "yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzSvBSFjNSiVHsuAA==".fromBase64() 72 | 73 | let check = data.crc24Checksum.toBase64() 74 | 75 | guard check == "njUN" else { 76 | XCTFail("mismatching checksum. got: \(check)") 77 | return 78 | } 79 | } 80 | 81 | //MARK: Packets 82 | 83 | func testPublicKeySerializeDeserializePacket() { 84 | do { 85 | let pubMsg = try AsciiArmorMessage(string: pubkey1) 86 | let packets = try [Packet](data: pubMsg.packetData) 87 | 88 | for packet in [packets[0], packets[4]] { 89 | let packetOriginal = packet 90 | let pubKeyOriginal = try PublicKey(packet: packetOriginal) 91 | 92 | let packetSerialized = try pubKeyOriginal.toPacket() 93 | let _ = try PublicKey(packet: packetSerialized) 94 | 95 | guard packetSerialized.body == packetOriginal.body else { 96 | print("original: \(packetOriginal.body.bytes)") 97 | print("serialized: \(packetSerialized.body.bytes)") 98 | XCTFail("packets differ after serialization deserialization") 99 | return 100 | 101 | } 102 | 103 | } 104 | 105 | } catch { 106 | XCTFail("Unexpected error: \(error)") 107 | 108 | } 109 | } 110 | 111 | func testPublicKeyTwoSerializeDeserializePacket() { 112 | do { 113 | let pubMsg = try AsciiArmorMessage(string: pubkey2) 114 | let packets = try [Packet](data: pubMsg.packetData) 115 | 116 | for packet in [packets[0]] { 117 | let packetOriginal = packet 118 | let pubKeyOriginal = try PublicKey(packet: packetOriginal) 119 | 120 | let packetSerialized = try pubKeyOriginal.toPacket() 121 | let _ = try PublicKey(packet: packetSerialized) 122 | 123 | guard packetSerialized.body == packetOriginal.body else { 124 | print("original: \(packetOriginal.body.bytes)") 125 | print("serialized: \(packetSerialized.body.bytes)") 126 | XCTFail("packets differ after serialization deserialization") 127 | return 128 | 129 | } 130 | 131 | } 132 | 133 | } catch { 134 | XCTFail("Unexpected error: \(error)") 135 | 136 | } 137 | } 138 | 139 | func testPublicKeyEd25519SerializeDeserializePacket() { 140 | do { 141 | let pubMsg = try AsciiArmorMessage(string: pubkeyEd25519) 142 | let packets = try [Packet](data: pubMsg.packetData) 143 | 144 | let packetOriginal = packets[0] 145 | let pubKeyOriginal = try PublicKey(packet: packetOriginal) 146 | 147 | let packetSerialized = try pubKeyOriginal.toPacket() 148 | let _ = try PublicKey(packet: packetSerialized) 149 | 150 | guard packetSerialized.body == packetOriginal.body else { 151 | print("original: \(packetOriginal.body.bytes)") 152 | print("serialized: \(packetSerialized.body.bytes)") 153 | XCTFail("packets differ after serialization deserialization") 154 | return 155 | 156 | } 157 | } catch { 158 | XCTFail("Unexpected error: \(error)") 159 | } 160 | } 161 | 162 | func testPublicKeyNISTP256SerializeDeserializePacket() { 163 | do { 164 | let pubMsg = try AsciiArmorMessage(string: pubkeyNISTP256) 165 | let packets = try [Packet](data: pubMsg.packetData) 166 | 167 | let packetOriginal = packets[0] 168 | let pubKeyOriginal = try PublicKey(packet: packetOriginal) 169 | 170 | let packetSerialized = try pubKeyOriginal.toPacket() 171 | let _ = try PublicKey(packet: packetSerialized) 172 | 173 | guard packetSerialized.body == packetOriginal.body else { 174 | print("original: \(packetOriginal.body.bytes)") 175 | print("serialized: \(packetSerialized.body.bytes)") 176 | XCTFail("packets differ after serialization deserialization") 177 | return 178 | 179 | } 180 | } catch { 181 | XCTFail("Unexpected error: \(error)") 182 | } 183 | } 184 | 185 | 186 | 187 | func testFingerprintAndKeyId() { 188 | do { 189 | let pubMsg = try AsciiArmorMessage(string: pubkey1) 190 | let packets = try [Packet](data: pubMsg.packetData) 191 | 192 | for packet in [packets[0]] { 193 | let packetOriginal = packet 194 | let pubKeyOriginal = try PublicKey(packet: packetOriginal) 195 | 196 | let fp = pubKeyOriginal.fingerprint().hex 197 | let keyID = try pubKeyOriginal.keyID().hex 198 | 199 | guard fp.uppercased() == "F7A83D5CE65C42817A4AB7647A1037F5EF07891E" else { 200 | XCTFail("Fingerprint does not match, got: \(fp)") 201 | return 202 | } 203 | 204 | guard keyID.uppercased() == "7A1037F5EF07891E" else { 205 | XCTFail("KeyID does not match, got: \(keyID)") 206 | return 207 | } 208 | 209 | } 210 | 211 | } catch { 212 | XCTFail("Unexpected error: \(error)") 213 | 214 | } 215 | } 216 | 217 | 218 | // UserID 219 | func testUserIDPacket() { 220 | do { 221 | let pubMsg = try AsciiArmorMessage(string: pubkey1) 222 | let packets = try [Packet](data: pubMsg.packetData) 223 | 224 | let packetOriginal = packets[1] 225 | let userIDOriginal = try UserID(packet: packetOriginal) 226 | 227 | let packetSerialized = try userIDOriginal.toPacket() 228 | let _ = try UserID(packet: packetSerialized) 229 | 230 | guard packetSerialized.body == packetOriginal.body else { 231 | print("original: \(packetOriginal.body.bytes)") 232 | print("serialized: \(packetSerialized.body.bytes)") 233 | XCTFail("packets differ after serialization deserialization") 234 | return 235 | 236 | } 237 | 238 | } catch { 239 | XCTFail("Unexpected error: \(error)") 240 | 241 | } 242 | } 243 | 244 | // Signature 245 | func testSignatureSerializeDeserializePacket() { 246 | do { 247 | let pubMsg = try AsciiArmorMessage(string: pubkey1) 248 | let packets = try [Packet](data: pubMsg.packetData) 249 | 250 | for packet in [packets[2], packets[3], packets[5]] { 251 | let packetOriginal = packet 252 | let sigOriginal = try Signature(packet: packetOriginal) 253 | 254 | let packetSerialized = try sigOriginal.toPacket() 255 | let _ = try Signature(packet: packetSerialized) 256 | 257 | guard packetSerialized.body == packetOriginal.body else { 258 | print("original: \(packetOriginal.body.bytes)") 259 | print("serialized: \(packetSerialized.body.bytes)") 260 | XCTFail("packets differ after serialization deserialization") 261 | return 262 | 263 | } 264 | } 265 | 266 | } catch { 267 | XCTFail("Unexpected error: \(error)") 268 | 269 | } 270 | } 271 | 272 | func testSignatureSerializeDeserializeEd25519Packet() { 273 | do { 274 | let pubMsg = try AsciiArmorMessage(string: pubkeyEd25519) 275 | let packets = try [Packet](data: pubMsg.packetData) 276 | 277 | let packetOriginal = packets[2] 278 | let sigOriginal = try Signature(packet: packetOriginal) 279 | 280 | let packetSerialized = try sigOriginal.toPacket() 281 | let _ = try Signature(packet: packetSerialized) 282 | 283 | guard packetSerialized.body == packetOriginal.body else { 284 | print("original: \(packetOriginal.body.bytes)") 285 | print("serialized: \(packetSerialized.body.bytes)") 286 | XCTFail("packets differ after serialization deserialization") 287 | return 288 | 289 | } 290 | 291 | } catch { 292 | XCTFail("Unexpected error: \(error)") 293 | 294 | } 295 | } 296 | 297 | 298 | // OnePassSignature 299 | func testOnePassSignatureSerializeDeserialize() { 300 | do { 301 | let sigMsg = try AsciiArmorMessage(string: attachedSignature) 302 | let packets = try Message(data: sigMsg.packetData).packets 303 | 304 | let packetOriginal = packets[0] 305 | let sigOriginal = try OnePassSignature(packet: packetOriginal) 306 | 307 | let packetSerialized = try sigOriginal.toPacket() 308 | let _ = try OnePassSignature(packet: packetSerialized) 309 | 310 | guard packetSerialized.body == packetOriginal.body else { 311 | print("original: \(packetOriginal.body.bytes)") 312 | print("serialized: \(packetSerialized.body.bytes)") 313 | XCTFail("packets differ after serialization deserialization") 314 | return 315 | } 316 | 317 | } catch { 318 | XCTFail("Unexpected error: \(error)") 319 | } 320 | } 321 | 322 | func testLiteralDataPacket() { 323 | do { 324 | let sigMsg = try AsciiArmorMessage(string: attachedSignature) 325 | let packets = try Message(data: sigMsg.packetData).packets 326 | 327 | let packetOriginal = packets[1] 328 | let literalData = try LiteralData(packet: packetOriginal) 329 | 330 | // test contents 331 | guard let utf8Contents = String(data: literalData.contents, encoding: String.Encoding.utf8) else { 332 | XCTFail("literal data invalid utf8") 333 | return 334 | } 335 | 336 | // test contents match 337 | XCTAssert(utf8Contents == "AAAA") 338 | 339 | // test serialize matches 340 | let packetSerialized = try literalData.toPacket() 341 | let _ = try LiteralData(packet: packetSerialized) 342 | 343 | guard packetSerialized.body == packetOriginal.body else { 344 | print("original: \(packetOriginal.body.bytes)") 345 | print("serialized: \(packetSerialized.body.bytes)") 346 | XCTFail("packets differ after serialization deserialization") 347 | return 348 | } 349 | 350 | } catch { 351 | XCTFail("Unexpected error: \(error)") 352 | } 353 | } 354 | 355 | 356 | 357 | func testAttachedSignatureHashLeftTwoBytes() { 358 | do { 359 | let sigMsg = try AsciiArmorMessage(string: attachedSignature) 360 | let packets = try Message(data: sigMsg.packetData).packets 361 | 362 | let onePass = try OnePassSignature(packet: packets[0]) 363 | let literalData = try LiteralData(packet: packets[1]) 364 | let signature = try Signature(packet: packets[2]) 365 | 366 | var signedAttachedBinary = SignedAttachedBinaryDocument(binaryData: literalData.contents, binaryDate: literalData.date, keyID: onePass.keyID, publicKeyAlgorithm: signature.publicKeyAlgorithm, hashAlgorithm: signature.hashAlgorithm, hashedSubpacketables: signature.hashedSubpacketables) 367 | 368 | let dataToHash = try signedAttachedBinary.dataToHash() 369 | 370 | var hash:Data 371 | 372 | switch signature.hashAlgorithm { 373 | case .sha1: 374 | hash = dataToHash.SHA1 375 | case .sha224: 376 | hash = dataToHash.SHA224 377 | case .sha256: 378 | hash = dataToHash.SHA256 379 | case .sha384: 380 | hash = dataToHash.SHA384 381 | case .sha512: 382 | hash = dataToHash.SHA512 383 | } 384 | 385 | try signedAttachedBinary.set(hash: hash, signedHash: signature.signature) 386 | 387 | guard signedAttachedBinary.signature.leftTwoHashBytes == signature.leftTwoHashBytes else { 388 | XCTFail("Left two hash bytes don't match: \nGot: \(signedAttachedBinary.signature.leftTwoHashBytes)\nExpected: \(signature.leftTwoHashBytes)") 389 | return 390 | } 391 | 392 | } catch { 393 | XCTFail("Unexpected error: \(error)") 394 | 395 | } 396 | } 397 | 398 | 399 | // Test RSA left two bytes match 400 | func testRSAPublicKeySignatureHashLeftTwoBytes() { 401 | do { 402 | let pubMsg = try AsciiArmorMessage(string: pubkey1) 403 | let packets = try [Packet](data: pubMsg.packetData) 404 | 405 | let publicKey = try PublicKey(packet: packets[0]) 406 | let userID = try UserID(packet: packets[1]) 407 | let signature = try Signature(packet: packets[3]) 408 | 409 | var signedPubKey = try SignedPublicKeyIdentity(publicKey: publicKey, userID: userID, hashAlgorithm: signature.hashAlgorithm, hashedSubpacketables: signature.hashedSubpacketables) 410 | 411 | let dataToHash = try signedPubKey.dataToHash() 412 | 413 | var hash:Data 414 | 415 | switch signature.hashAlgorithm { 416 | case .sha1: 417 | hash = dataToHash.SHA1 418 | case .sha224: 419 | hash = dataToHash.SHA224 420 | case .sha256: 421 | hash = dataToHash.SHA256 422 | case .sha384: 423 | hash = dataToHash.SHA384 424 | case .sha512: 425 | hash = dataToHash.SHA512 426 | } 427 | 428 | try signedPubKey.set(hash: hash, signedHash: signature.signature) 429 | 430 | guard signedPubKey.signature.leftTwoHashBytes == signature.leftTwoHashBytes else { 431 | XCTFail("Left two hash bytes don't match: \nGot: \(signedPubKey.signature.leftTwoHashBytes)\nExpected: \(signature.leftTwoHashBytes)") 432 | return 433 | } 434 | 435 | let outMsg = try AsciiArmorMessage(message: signedPubKey.toMessage(), blockType: ArmorMessageBlock.publicKey).toString() 436 | let _ = try [Packet](data: AsciiArmorMessage(string: outMsg).packetData) 437 | 438 | print(outMsg) 439 | 440 | 441 | } catch { 442 | XCTFail("Unexpected error: \(error)") 443 | } 444 | } 445 | 446 | 447 | // Test signature hash 448 | func testEd25519SignatureHashMatchesLeft16Bits() { 449 | do { 450 | let pubMsg = try AsciiArmorMessage(string: pubkeyEd25519) 451 | let packets = try [Packet](data: pubMsg.packetData) 452 | 453 | let publicKey = try PublicKey(packet: packets[0]) 454 | let userID = try UserID(packet: packets[1]) 455 | let signature = try Signature(packet: packets[2]) 456 | 457 | var signedPubKey = try SignedPublicKeyIdentity(publicKey: publicKey, userID: userID, hashAlgorithm: signature.hashAlgorithm, hashedSubpacketables: signature.hashedSubpacketables) 458 | 459 | let dataToHash = try signedPubKey.dataToHash() 460 | 461 | var hash:Data 462 | 463 | switch signature.hashAlgorithm { 464 | case .sha1: 465 | hash = dataToHash.SHA1 466 | case .sha224: 467 | hash = dataToHash.SHA224 468 | case .sha256: 469 | hash = dataToHash.SHA256 470 | case .sha384: 471 | hash = dataToHash.SHA384 472 | case .sha512: 473 | hash = dataToHash.SHA512 474 | } 475 | 476 | try signedPubKey.set(hash: hash, signedHash: signature.signature) 477 | 478 | guard signedPubKey.signature.leftTwoHashBytes == signature.leftTwoHashBytes else { 479 | XCTFail("Left two hash bytes don't match: \nGot: \(signedPubKey.signature.leftTwoHashBytes)\nExpected: \(signature.leftTwoHashBytes)") 480 | return 481 | } 482 | 483 | 484 | } catch { 485 | XCTFail("Unexpected error: \(error)") 486 | 487 | } 488 | } 489 | 490 | func testNISTP256SignatureHashMatchesLeft16Bits() { 491 | do { 492 | let pubMsg = try AsciiArmorMessage(string: pubkeyNISTP256) 493 | let packets = try [Packet](data: pubMsg.packetData) 494 | 495 | let publicKey = try PublicKey(packet: packets[0]) 496 | let userID = try UserID(packet: packets[1]) 497 | let signature = try Signature(packet: packets[2]) 498 | 499 | var signedPubKey = try SignedPublicKeyIdentity(publicKey: publicKey, userID: userID, hashAlgorithm: signature.hashAlgorithm, hashedSubpacketables: signature.hashedSubpacketables) 500 | let dataToHash = try signedPubKey.dataToHash() 501 | 502 | var hash:Data 503 | 504 | switch signature.hashAlgorithm { 505 | case .sha1: 506 | hash = dataToHash.SHA1 507 | case .sha224: 508 | hash = dataToHash.SHA224 509 | case .sha256: 510 | hash = dataToHash.SHA256 511 | case .sha384: 512 | hash = dataToHash.SHA384 513 | case .sha512: 514 | hash = dataToHash.SHA512 515 | } 516 | 517 | try signedPubKey.set(hash: hash, signedHash: signature.signature) 518 | 519 | guard signedPubKey.signature.leftTwoHashBytes == signature.leftTwoHashBytes else { 520 | XCTFail("Left two hash bytes don't match: \nGot: \(signedPubKey.signature.leftTwoHashBytes)\nExpected: \(signature.leftTwoHashBytes)") 521 | return 522 | } 523 | 524 | 525 | } catch { 526 | XCTFail("Unexpected error: \(error)") 527 | 528 | } 529 | } 530 | 531 | // Test binary document signature 532 | func testBinaryDocumentSignature() { 533 | do { 534 | let msg = try AsciiArmorMessage(string: binarySignature) 535 | let packets = try [Packet](data: msg.packetData) 536 | let signature = try Signature(packet: packets[0]) 537 | 538 | print("Kind: \(signature.kind)") 539 | print("Hashed Sbpkt Type: \(signature.hashedSubpacketables[0].type)") 540 | 541 | let binaryData = binaryDocument.data(using: String.Encoding.utf8)! 542 | 543 | var signedBinary = SignedBinaryDocument(binary: binaryData, publicKeyAlgorithm: signature.publicKeyAlgorithm, hashAlgorithm: signature.hashAlgorithm, hashedSubpacketables: signature.hashedSubpacketables) 544 | 545 | let dataToHash = try signedBinary.dataToHash() 546 | 547 | var hash:Data 548 | switch signature.hashAlgorithm { 549 | case .sha1: 550 | hash = dataToHash.SHA1 551 | case .sha224: 552 | hash = dataToHash.SHA224 553 | case .sha256: 554 | hash = dataToHash.SHA256 555 | case .sha384: 556 | hash = dataToHash.SHA384 557 | case .sha512: 558 | hash = dataToHash.SHA512 559 | } 560 | 561 | try signedBinary.set(hash: hash, signedHash: signature.signature) 562 | 563 | guard signedBinary.signature.leftTwoHashBytes == signature.leftTwoHashBytes else { 564 | XCTFail("Left two hash bytes don't match: \nGot: \(signedBinary.signature.leftTwoHashBytes)\nExpected: \(signature.leftTwoHashBytes)") 565 | return 566 | } 567 | 568 | 569 | } catch { 570 | XCTFail("Unexpected error: \(error)") 571 | } 572 | } 573 | 574 | func testOldPacketLengthSerialization() { 575 | do { 576 | do { 577 | let length = try PacketLength(body: 100) 578 | guard case .old(let l) = length.length, 579 | l == .oneOctet, 580 | length.formatBytes == [UInt8]([0x64]) else { 581 | XCTFail("incorrect length") 582 | return 583 | } 584 | } 585 | do { 586 | let length = try PacketLength(body: 1723) 587 | guard case .old(let l) = length.length, 588 | l == .twoOctet, 589 | length.formatBytes == [UInt8]([0x06, 0xBB]) else { 590 | XCTFail("incorrect length") 591 | return 592 | } 593 | } 594 | do { 595 | let length = try PacketLength(body: 100000) 596 | guard case .old(let l) = length.length, 597 | l == .fourOctet, 598 | length.formatBytes == [UInt8]([0x00, 0x01, 0x86, 0xA0]) else { 599 | XCTFail("incorrect length") 600 | return 601 | } 602 | } 603 | } catch { 604 | XCTFail("Unexpected error: \(error)") 605 | } 606 | } 607 | 608 | func testOldPacketLengthDeserialization() { 609 | do { 610 | do { 611 | let length = try PacketLength(oldFormat: [0x64], type: PacketLength.OldFormatType.oneOctet.rawValue) 612 | guard length.body == 100 else { 613 | XCTFail("incorrect length") 614 | return 615 | } 616 | } 617 | do { 618 | let length = try PacketLength(oldFormat: [0x06, 0xBB], type: PacketLength.OldFormatType.twoOctet.rawValue) 619 | guard length.body == 1723 else { 620 | XCTFail("incorrect length") 621 | return 622 | } 623 | } 624 | do { 625 | let length = try PacketLength(oldFormat: [0x00, 0x01, 0x86, 0xA0], type: PacketLength.OldFormatType.fourOctet.rawValue) 626 | guard length.body == 100000 else { 627 | XCTFail("incorrect length") 628 | return 629 | } 630 | } 631 | } catch { 632 | XCTFail("Unexpected error: \(error)") 633 | } 634 | } 635 | 636 | func testNewPacketLengthDeserialization() { 637 | do { 638 | do { 639 | let length = try PacketLength(newFormat: [0x64]) 640 | guard length.body == 100 else { 641 | XCTFail("incorrect length") 642 | return 643 | } 644 | } 645 | do { 646 | let length = try PacketLength(newFormat: [0xC5, 0xFB]) 647 | guard length.body == 1723 else { 648 | XCTFail("incorrect length") 649 | return 650 | } 651 | } 652 | do { 653 | let length = try PacketLength(newFormat: [0xFF, 0x00, 0x01, 0x86, 0xA0]) 654 | guard length.body == 100000 else { 655 | XCTFail("incorrect length") 656 | return 657 | } 658 | } 659 | } catch { 660 | XCTFail("Unexpected error: \(error)") 661 | } 662 | } 663 | 664 | /** 665 | MPInt Tests 666 | */ 667 | func testMPInt() { 668 | 669 | var data = Data([0x0E, 0xAD, 0xBE, 0xEF]) 670 | var mpint = MPInt(integerData: data) 671 | 672 | XCTAssert(mpint.data.bytes == data.bytes, "mismatch bytes: \(mpint.data.bytes)") 673 | XCTAssert(mpint.byteLength == 2 + data.count, "incorrect byte length: \(mpint.byteLength)") 674 | XCTAssert(mpint.lengthBytes == 28.twoByteBigEndianBytes(), "incorrect length bytes: \(mpint.lengthBytes)") 675 | 676 | 677 | data = Data([0xDE, 0xAD, 0xBE, 0xEF]) 678 | mpint = MPInt(integerData: data) 679 | 680 | XCTAssert(mpint.data.bytes == data.bytes, "mismatch bytes: \(mpint.data.bytes)") 681 | XCTAssert(mpint.byteLength == 2 + data.count, "incorrect byte length: \(mpint.byteLength)") 682 | XCTAssert(mpint.lengthBytes == 32.twoByteBigEndianBytes(), "incorrect length bytes: \(mpint.lengthBytes)") 683 | 684 | 685 | // test leading zeros 686 | data = Data([0x00, 0xAD, 0xBE, 0xEF]) 687 | mpint = MPInt(integerData: data) 688 | 689 | XCTAssert(mpint.data.bytes == [UInt8](data.bytes[1 ..< data.count]), "mismatch bytes: \(mpint.data.bytes)") 690 | XCTAssert(mpint.byteLength == 2 + 3 , "incorrect byte length: \(mpint.byteLength)") 691 | XCTAssert(mpint.lengthBytes == 24.twoByteBigEndianBytes(), "incorrect length bytes: \(mpint.lengthBytes)") 692 | 693 | 694 | data = Data([0x00, 0x00, 0x2E, 0xEF]) 695 | mpint = MPInt(integerData: data) 696 | 697 | XCTAssert(mpint.data.bytes == [UInt8](data.bytes[2 ..< data.count]), "mismatch bytes: \(mpint.data.bytes)") 698 | XCTAssert(mpint.byteLength == 2 + 2, "incorrect byte length: \(mpint.byteLength)") 699 | XCTAssert(mpint.lengthBytes == 14.twoByteBigEndianBytes(), "incorrect length bytes: \(mpint.lengthBytes)") 700 | 701 | // all 0's 702 | data = Data([0x00, 0x00, 0x00, 0x00]) 703 | mpint = MPInt(integerData: data) 704 | 705 | XCTAssert(mpint.data.bytes == [], "mismatch bytes: \(mpint.data.bytes)") 706 | XCTAssert(mpint.byteLength == 2, "incorrect byte length: \(mpint.byteLength)") 707 | XCTAssert(mpint.lengthBytes == 0.twoByteBigEndianBytes(), "incorrect length bytes: \(mpint.lengthBytes)") 708 | XCTAssert(data.numBits == 0, "numBits fails on all )s: \(mpint.data.bytes)") 709 | 710 | // random values 711 | for _ in 0 ..< 256 { 712 | data = Data.random(size: 128) 713 | mpint = MPInt(integerData: data) 714 | 715 | // strip any leading 0s 716 | var startingIndex = 0 717 | for byte in data.bytes { 718 | guard Int(byte) == 0 else { 719 | break 720 | } 721 | 722 | startingIndex += 1 723 | } 724 | 725 | XCTAssert(mpint.data.bytes == [UInt8](data.bytes[startingIndex ..< data.count]), "mismatch bytes: \(mpint.data.bytes)") 726 | XCTAssert(mpint.byteLength == 2 + (data.count - startingIndex), "incorrect byte length: \(mpint.byteLength)") 727 | XCTAssert(mpint.lengthBytes == data.numBits.twoByteBigEndianBytes(), "incorrect length bytes: \(mpint.lengthBytes)") 728 | } 729 | } 730 | 731 | } 732 | 733 | extension Data { 734 | 735 | static func random(size:Int) -> Data { 736 | var result = [UInt8](repeating: 0, count: size) 737 | let _ = SecRandomCopyBytes(kSecRandomDefault, size, &result) 738 | return Data(result) 739 | } 740 | } 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | -------------------------------------------------------------------------------- /PGPFormatTests/attached_signature.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP MESSAGE----- 2 | Comment: GPGTools - https://gpgtools.org 3 | 4 | kA0DAAoB2sf8oicL38IBywpiAFlJivBBQUFBiQIcBAABCgAGBQJZSYrwAAoJENrH 5 | /KInC9/C7E4P/3ePeRXDe4q/5GPQIf6V4kmj3GhVDL6rbYCANqEusKdklQuJW5Jf 6 | M5P9WxVlFHeMbhFZLZmvbXqxkb64LQN2hMCSDcwQVxezJ2+wBtPpIRDmBNDwZc9B 7 | iBx+2FrnNdhicQM1tFyDTYvaWUPt8ljua6W4Aot07mo11PpSXJicXxxgmzBaJM4I 8 | NuyTCuvuBMSszsuaoqZVw6h3c9yG40NVuc9cRMmi+bQvSdb4F8MNu4lKA+uUmgLa 9 | r27VQgcMffrFXfm8HWSLFpGV3sSJj9/FijWvSfJ+7Dx8j0XATRNjSxYtln8xanWd 10 | GMUkU+0T9v6MB4MO9AuuSdv44OOh5pc3TrB5mUjOvxjOdQ4fiJxctOEV5xgSH6cd 11 | A+cwjAO6+HHCyytZZpH3W3l7cu3+iAt7CRY7RqnX6mjAVNddeJ3hnlVJc6XJ+W4D 12 | Dc2OPLNpk4g/vpKEPJx5LwjP+kd68N9XosBXDg3mg4wHwyhdOMAZR9DDn+9rren0 13 | XsdYySARdjr2beppLgpPdTcEQibt4ilZ6bmk5WryHuAR+24ZM6djV/Xy0qrwOt1V 14 | HDpjuGi2G1OdvJW4DvDKme2fcolP+K76yyiEu6qB+QBhfbwimK3PT2+hgisxoYDJ 15 | FOPg9MX48LBEE94MkcEIyAmF1HV3kb2XqMIWptU/5d8+zR6bNGoPSYcC 16 | =Rz9X 17 | -----END PGP MESSAGE----- 18 | -------------------------------------------------------------------------------- /PGPFormatTests/pubkey1.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Comment: Some test hello 3 | 4 | mQENBFNdmhIBCAC5E6OdPAUvG15JHMST4gl+O1w5FCOoEa6zuO/032xpa6B58eR8 5 | xrS5ac4l9TUqQViJEKiL140IxLe3KyiiA9TckTSzbbjHbpmIuAebUqaquVx9wi20 6 | iSoaLIrqdNQKrDn+ByhZv7TAMAqK3kfg3Ofi/BQ1NJMnLrZ9VMr20EHtb+vXB7b/ 7 | pdiYLPexeY03ewGZf7tZwMt5f/PsRngtH1ryIWDDXWsFprReuImBuuXVS+K3mEgQ 8 | REbBUudNChxoVvK4FbguR6dnQLKCkwX1uNPkjpA4RhROPB6dzjYFgRE3tcIZUZGf 9 | ofY+Xgcw6qnc4zQN9ED0eVbbkOm/aFgyTZN/ABEBAAG0IUtldmluIEtpbmcgPDRr 10 | ZXZpbmtpbmdAZ21haWwuY29tPokBHAQTAQIABgUCU2g3zQAKCRCYNjQs8yzl7APx 11 | CACAlHHvIix/SvPRGQSNOMpuC07TiI9TRkCt87w+2Ye9VRhpQcOXswTFAVpzloGC 12 | MW0hbcBF+iciVC4GB2+u7bgQxwBk8ifGZEfbmhh1b2NJkn3w9wlFk8A0b7/f5+O3 13 | 0XW7Aj+sNewanVGLoFzZkW/R2p52b+2/IwXkeBrX3hzRw7lRUPATsDdR+IwBYQ/G 14 | aDXhK2MZkQgzFzkZi2V6uC5Gv/vCdwP5wjTUGAqeTHLB5zVoF1cMwmYetQ8mFXu6 15 | 5RAeNOMCt9fz97lnz7t0KGziTFreB8Iukh0re+br6O6WakEdEHuAwUMR7bxWxZdI 16 | aDmUeA4trOEHG9xBiLV4xyquiQE9BBMBCgAnBQJTXZoSAhsDBQkHhh+ABQsJCAcD 17 | BRUKCQgLBRYCAwEAAh4BAheAAAoJEHoQN/XvB4kep7wH/iAOMmjEbbj96QDwjKNN 18 | x9VHcp5rW8VAgtNIOTALRKpzHgE4eniLcweVcWvHnsk2/sIGhy0PUVnMCX4qMm3A 19 | bnRGD8KD3dw2WPWy+uoMIMPuyASXWujwUMh4GjzjppPa7jIv0QwH4gEv7+ro0WHu 20 | R47EP1PEO3ZRpY7trV+YQs/pjGtSWewI+G3WfBuYGcmoQf5fR4fx/UfMqFx9/OFq 21 | GMnb8fEDA3LU0nCdfWDIOjgE84RRf9YpSSB4wspSLyR0iJiZ+/Cep0ot/ztiLiCw 22 | cldIDIntLxk7Ti2o9uz/sEEtnTI9vJEnz9/fyC4TM963T5Q7qkHR/5gZ8mAfAnlr 23 | gXm5AQ0EU12aEgEIAMBKbgMgbFjVdYVpSwAmXHEblHxs4aVviNmseKNH4mssGxp+ 24 | yVurH4/sV0u13hnuT/GfMDa+T+CqA08i75iFBb9UmqTqVODwPmz919eiNb5rkyLu 25 | 7WcZMavBTX8Lf+cN2MKMhRISTSkxDZDWZJz5lqMxO+c1QtyTAmdR883JgRh8jdWP 26 | rTQLO4mQY2t2/vBZP2LFdZ/R81hXGkreSgYlE8qC5VnMQB5UjqUOHdAWC0fZY3PR 27 | PCib5mcufqAY6PgOlTkOEKdlnGx36AAwyHSo7OlXkzJt3vH2EujAyZG3c90ducH+ 28 | UH+ai3oHDQeBugAgsyf64Em6RnGEzJf8TigBEckAEQEAAYkBJQQYAQoADwUCU12a 29 | EgIbDAUJB4YfgAAKCRB6EDf17weJHmCBCACxxyhSdcM136daMOvXDByn9kSJcCOT 30 | iT4Hd6v0vgzh5ynz4zBRO1hzRXRNiiJtLMCZza9ftG1O2G0EyBhMHkKT8J3i3RCw 31 | 8HF8Fd3yB2KYs0kaFGsYzZkzXCXaqjFLXJJqmLVTOUGScX8x2nrYWJXn4fBJUA8N 32 | d3EUC/3fSqqZTjq3BEE01gzy6wruGKVqoSziXWKm4PcKO32zRoMi2Hh7hIjYqzWq 33 | aZ5Pg/NzdwlX65sUVar1GzKbuHmxMgC8JtZvidqh/qoJk45tzIwj4GdIBbJbiSw9 34 | N0haqhW7w51HixXuh9SGaJTGfGccg1Zkw7fYZkUzusTtsFXhfcx/9S3Y 35 | =m4zw 36 | -----END PGP PUBLIC KEY BLOCK----- 37 | -------------------------------------------------------------------------------- /PGPFormatTests/pubkey2.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Comment: Created by swift-pgp 3 | 4 | mQINBFkf1JQDEADeo0zoD6v3Fm04AEigtP2FzH0Kce1LMiTGxvTBLiFeaXmBNmKJ 5 | J2h4DGYUTwCcS/5OCGNZVfUxrgW8TuOo/4nKm5IH2klWpT0wzMEK2VJ2fcrUmacI 6 | MaVYxXRw8IEqZBX7Lr9QqPyxluJB11haiYEsaGPzRTdnXo1evoTH1uuvUkdi8uEF 7 | QYGWl4dvafupdoetnPSCZu0r/g8SLHDCFwtypTj255tXd2iQmq3Lwk6/saISgpJV 8 | qZOoKtofuWQTgcEcmy8HfvIMCYbNK3rHq85r04nBcwj7opf6Kd9SJa30SpOq+U0g 9 | mBX0452HHR8+APd7W+gNEIfTJMyQblF/7kaFWEdPkHPogHmagH+RM0NUA9DZ7La4 10 | tdUnFr7Bazsk/MMG+TK5zmtsBF15CkaCjNyGnAWxFnJurn4gsIXWOW00oJqKTmlI 11 | PnpMW9sLJJLpH9xD12RNY/qMe+LcNT6LaM1MKfY9zyKP7gqEGhxH7cHfeBRcJ6Rt 12 | Xrz3ZPtesW/9HpshI5oAPrMK275EfiR2wXN7e7mrLwqfth3K06O9XV9+WkZAjIgz 13 | 16AARbcy6qeRhYW9s8SqySrsbF8KResSLo+EbB8BL+P9UUjzWO/nc919MwVBXeqM 14 | 2R4h0SLmRodBzv+iU2go60+TlYH6bFrTfEE3G5n5l+6onlc0TAkvuNmhowARAQAB 15 | tBthbGV4IGdyaW5tYW4gPG1lQGFsZXhnci5pbj6JAhwEEwMCAAYFAlkf1JQACgkQ 16 | zTc/IxsJXFIhsw//Y8yBJGtdV0WbBIP5de5AKhYb7qB4F2LSmRK6z3RY4UbP7UW2 17 | 5iM0jUQOtqIqeYxeZLaCAUKOQ1oMUhnopSAv/JKC3afidmJScToiYGq7jVwYrFsS 18 | 69drv1M0CTMU5TvsMFSBotSFMl3MxrC1E6V5FezqYsHk73h80KdFogq/0n9z9Frv 19 | BYsgvIhPZGikmj7AsmIuQENSrSX9pdVUAXTSmRwz+FYUneykqEyxGG13E7+A/YAv 20 | kN2Wbv+DQ224iq61YhazfCL3amUjh+xSL6Oj0ksAS70PAXMGQoJTul4mHDVxlO7+ 21 | pD4MT51JjlvfKutMHPP1w2mJFbkUzvOIo0jGPq2XS/CB6T1Cu99sCis7XreTx8q4 22 | nvbi/CNnvlSy99mhqzYTbNUKJEmisXfQkiAoRQWbzB7FAS0LtBdo5okYI4vFS64Z 23 | VE8DeWz7cKhRi2FkZzNVvyIM5R86WUtnewu8SWaXywBEXydyeIqvX8Cexa2wwPlD 24 | 1NbQyj8XgFEIgASBwmefM8CANpCIVC2BCvSxekkRKz71Y1C/i+yck9sLCabEWbGd 25 | lBji3fC2sCqqJyWepDym9YmpoWDCNb+1zcbOsO+N2OW24njmv7YcEE4SwWCOoll8 26 | +PdoQROiBkM0Pab2sxT4ptymNA4XwXIczORqlJMFkc5j7QKroXG5v/TvdUk= 27 | =HOsV 28 | -----END PGP PUBLIC KEY BLOCK----- 29 | -------------------------------------------------------------------------------- /PGPFormatTests/pubkey3.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Comment: GPGTools - https://gpgtools.org 3 | 4 | mDMEWR/vcRYJKwYBBAHaRw8BAQdAczcZVkPEdUEYYLVr6sr8ruEaGPo0N2w/wHSB 5 | UBpof2q0J0FsZXggRWQyNTUxOSAodGVzdCkgPGVkMjU1MTlAYWxleGdyLmluPoiQ 6 | BBMWCgA4FiEEIXCAaBWccFrnOgoBmxZiGt3JRJAFAlkf73ECGwMFCwkIBwMFFQoJ 7 | CAsFFgIDAQACHgECF4AACgkQmxZiGt3JRJBdLQD/WVW+E+JYEapG8svSjk/vZZC+ 8 | jeSNLN1I97n8M3mueGkBAOmxBkmErMVnAPGNw9giZsaOmtSpZ0ceIpNMTE8CTB0L 9 | =Wqf1 10 | -----END PGP PUBLIC KEY BLOCK----- 11 | -------------------------------------------------------------------------------- /PGPFormatTests/pubkey4.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Comment: GPGTools - https://gpgtools.org 3 | 4 | mFIEWg8n8xMIKoZIzj0DAQcCAwRuiHJdpqgyghtWoR6o+90exJ1b6C3zMtn2qPmB 5 | KoWw/yoq59kOyc96aWk3JQxKzOtEqoSmOfeiWcX8ijzp+n4itBd4eHh4eCA8eHh4 6 | eHhAYWxleGdyLmluPoiQBBMTCgA4FiEEcuUCUjhfReNUEQjjknWObz+oSjsFAloP 7 | J/MCGwMFCwkIBwMFFQoJCAsFFgIDAQACHgECF4AACgkQknWObz+oSjsdSQD5AR7v 8 | kVyxJfAdHtn4QF+TMGjEg9k5jjwm36yabMiO1pYBALWxIe3eSgFXsemyF4IgI8bU 9 | nprEPvvqBwEfnBvVesdd 10 | =gtY0 11 | -----END PGP PUBLIC KEY BLOCK----- 12 | 13 | -------------------------------------------------------------------------------- /PGPFormatTests/signature.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNATURE----- 2 | iQIcBAABCgAGBQJZILCJAAoJEO31bzzC5uMwvgQQAItcIWAsEgcq+IEbDzGapLDu 3 | BWj1O67IIvS9/kswH1HfCptQSSQwEb7WU8/DnyYoQ3dVZYSpP6WOP1zRU5PE+4mF 4 | 6TUzbM9ribYbmZsac1HUgF2XWxQBcA6IBSQWlIo/Gvkd3rprMVoyZxZgeq4Tbiby 5 | bYssdF/7DoagDyy+2czZ8GYqxa3z2josHw6sx+OYmOWOn8AWyRq5eM+A5QRESkUl 6 | frRrG/ncQkZ6BU0ATqi706VSYEGPtP6Ia6tKjEYTZjN56akfgxfswjUCpSIoZd6C 7 | KUEi+qsZQnO54zZYbajyUo2ozmUOppTqMPsZPl+answEnQfZqVFv/mBOlEFVe9tO 8 | U5UL+KarHkDhUD51B/Sg4zSAYZM17j/FI47wiT1g0AzrCUsc5tLqWk7d119hrI5M 9 | jNAG76XiIsirj0MJRY0p3O/K6HhKWM+jmYq4rSuX5/pCTPqnedgHgNeZh6HcymjS 10 | cfqqkIzrjbjd3+6PuSIwNy0cOmHubitCj4+YG/p8lzVaLc/uCmxIGM2ouCJiDRaA 11 | sD8iyb2jsiwYxBHlvE1dk7sIMUwywI/ufjc488vRsVCNI2KKmWBCCZ4GIZS3iP7p 12 | TQA+FLWhvtWuNxFD+pUd/hxX0MODE+6gDMuNmIY2+Uhegjvhr1HgWFLSb2HeMi2/ 13 | BCUDirSuE9bXfNTmOYa6 14 | =zIXq 15 | -----END PGP SIGNATURE----- 16 | -------------------------------------------------------------------------------- /PGPFormatTests/signed_raw.txt: -------------------------------------------------------------------------------- 1 | tree f21ec9de97e3ff0a66a068682a176020e91dcf29 2 | parent 9d66027f0cbff220fdce8c2f9ab61aad4c65d6fa 3 | author Kevin King <4kevinking@gmail.com> 1495034240 -0400 4 | committer Kevin King <4kevinking@gmail.com> 1495314475 -0400 5 | 6 | add krgpg dir 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-pgp 2 | A pure1 Swift library for parsing and creating PGP [RFC 4880](https://tools.ietf.org/html/rfc4880) public keys, user ids, and signatures. This library is designed to be public-key-cryptography-implementation-agnostic. That is, you can use swift-pgp with any public-key crypto implementation you choose, provided it is either an RSA or Ed25519 cryptosystem. 3 | 4 | > **1**. Except for SHA hash functions from CommonCrypto. 5 | 6 | # Created For Krypton Core 7 | 8 | 9 | This library was created for __Krypton__. 10 | For more information, check out [krypt.co](https://krypt.co). 11 | 12 | # Supported Features 13 | Currently, swift-pgp only signatures for certifications and binary documents, but it's abstracted to support the full RFC 4880 spec, see the next section for whats in the pipeline. 14 | 15 | - Public Keys: parse and create PGP public keys 16 | - [x] RSA 17 | - [x] Ed25519 (via ext. [eddsa draft](https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-00)) 18 | 19 | - Signatures: parse and create PGP Signatures 20 | - [x] Certify Public Key <> User ID binding (aka Certification Signatures) 21 | - [x] Binary Documents 22 | 23 | - ASCII Armor: parse and create ASCII armored PGP messages 24 | 25 | # Coming Soon 26 | The next phase of swift-pgp is to support formatting PGP encrypted messages. This will add support for parsing and creating structures like: 27 | 28 | - [ ] Symmetric-Key Encrypted Session Keys 29 | - [ ] Symmetrically Encrypted Data 30 | 31 | # Installing 32 | 1. `git submodule init; git submodule add git@github.com:kryptco/swift-pgp 33 | 2. Drag PGPFormat.xcodeproj/ to your project (don't check copy files, they're already there) 34 | 3. Add `CommonCrypto` to your project: 35 | 36 | Go to Build Phases and add this `run script` phase: 37 | 38 | ```bash 39 | modulesDirectory=$DERIVED_FILES_DIR/modules 40 | modulesMap=$modulesDirectory/module.modulemap 41 | modulesMapTemp=$modulesDirectory/module.modulemap.tmp 42 | 43 | mkdir -p "$modulesDirectory" 44 | 45 | cat > "$modulesMapTemp" << MAP 46 | module CommonCrypto [system] { 47 | header "$SDKROOT/usr/include/CommonCrypto/CommonCrypto.h" 48 | export * 49 | } 50 | MAP 51 | 52 | diff "$modulesMapTemp" "$modulesMap" >/dev/null 2>/dev/null 53 | if [[ $? != 0 ]] ; then 54 | mv "$modulesMapTemp" "$modulesMap" 55 | else 56 | rm "$modulesMapTemp" 57 | fi 58 | ``` 59 | screen shot 2017-08-31 at 11 55 25 am 60 | 61 | 62 | # How to use swift-pgp 63 | Create signatures with swift-pgp by utilizing the `Signable` interface. The `Signable` interface is what makes the swift-pgp library public-key-cryptography-implementation-agnostic. 64 | 65 | ```swift 66 | /** 67 | Represents a structure that can be signed 68 | */ 69 | public protocol Signable { 70 | var signature:Signature { get set } 71 | func signableData() throws -> Data 72 | } 73 | ``` 74 | 75 | The `Signable` interface is extended to provide two useful functions 76 | - `func dataToHash() throws -> Data` 77 | - `mutating func set(hash:Data, signedHash:Data) throws` 78 | 79 | This lets you initialize a signable, extract the data that needs to be hashed via `dataToHash()`, hash it, sign it, and then set the hash and signature via `set(hash:Data, signedHash:Data)`. The `dataToHash()` function can also be used to extract the data that needs to be hashed to verify signatures. 80 | 81 | Currently, there are only two types of Signables: `SignedPublicKeyIdentity` and `SignedBinaryDocument`. 82 | 83 | # Examples 84 | Below are a few examples for creating certification and binary document signatures. 85 | 86 | ## `SignedPublicKeyIdentity` 87 | A Public Key <> User ID binding certification. 88 | 89 | ```swift 90 | // initialize a public key 91 | let publicKeyData = RSAPublicKey(modulus: ..., exponent: ...) 92 | let publicKey = try PublicKey(create: .rsaSignOnly, publicKeyData: publicKeyData, date: Date()) 93 | 94 | // initialize a user id 95 | let userID = UserID(name: "Alex Grinman", email: "hello@krypt.co") 96 | 97 | // initialize the signed public key 98 | var signedPublicKey = try SignedPublicKeyIdentity(publicKey: publicKey, userID: userID, hashAlgorithm: .sha512) 99 | 100 | // extract the data to hash and sign, sign it, and set it on the signedPublicKey 101 | let dataToHash = try signedPublicKey.dataToHash() 102 | let hash = H(dataToHash) // where H is your chosen (i.e. sha512) hash implementation 103 | let signatureData = X(dataToHash) // where X is your chosen RSA sign implementation 104 | 105 | try signedPublicKey.set(hash: hash, signedHash: signatureData) 106 | 107 | // get the ASCII armored message 108 | let asciiMessage = try signedPublicKey.armoredMessage(blockType: .publicKey, comment: "created with swift-pgp") 109 | ``` 110 | 111 | 112 | > **Multiple UserIDs** often certifications need to say that several User IDs are binded to a public key. swift-pgp provides a helper struct for this called `SignedPublicKeyIdentities` that is initialized with an list of `SignedPublicKeyIdentity`s. 113 | 114 | ## `SignedBinaryDocument` 115 | A signature for a binary document. 116 | 117 | ```swift 118 | // the bytes of the document being signed, i.e. 0xDEADBEEF 119 | let binaryData = Data(bytes: [0xDE, 0xAD, 0xBE, 0xEF]) 120 | 121 | var signedBinary = SignedBinaryDocument(binary: binaryData, publicKeyAlgorithm: .rsaSignOnly, hashAlgorithm: .sha512) 122 | 123 | // extract the data to hash and sign, sign it, and set it on the signedPublicKey 124 | let dataToHash = try signedBinary.dataToHash() 125 | let hash = H(dataToHash) // where H is your chosen (i.e. sha512) hash implementation 126 | let signatureData = X(dataToHash) // where X is your chosen RSA sign implementation 127 | 128 | // compile the signed public key packets 129 | try signedBinary.set(hash: hash, signedHash: signatureData) 130 | 131 | // return ascii armored signature 132 | let asciiMessage = try signedBinary.set(blockType: .signature, comment: "created with swift-pgp") 133 | ``` 134 | 135 | # License 136 | We are currently deciding on a license for swift-pgp. 137 | For now, the code is released under All Rights Reserved. 138 | --------------------------------------------------------------------------------