diff --git a/SwiftyRSA.xcodeproj/project.pbxproj b/SwiftyRSA.xcodeproj/project.pbxproj index 8eaed96..7c3f76f 100644 --- a/SwiftyRSA.xcodeproj/project.pbxproj +++ b/SwiftyRSA.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 48461D06FA57F540A6140BC66F457877 /* SwiftyRSA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E84F551B29670A456D12366AA4C8E5 /* SwiftyRSA.swift */; }; 54EC57310DAFFE47CAA52CAED8F387B9 /* SwiftyRSA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E84F551B29670A456D12366AA4C8E5 /* SwiftyRSA.swift */; }; 61C3EEC5859298BF282B0BA572B8FCAC /* NSData+SHA.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AAC360BC9A1D1773D59C8E91186044E /* NSData+SHA.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 6C24D5A1214B2FD7004C13F3 /* swiftyrsa-public.pub in Resources */ = {isa = PBXBuildFile; fileRef = 6C24D5A0214B2FD7004C13F3 /* swiftyrsa-public.pub */; }; 8E6B4DE19F56FBA2DEEE318C4EEEA1C6 /* NSData+SHA.m in Sources */ = {isa = PBXBuildFile; fileRef = D77B6899031A54A9F698D3430A599421 /* NSData+SHA.m */; }; A535C69BBF632C0A67A6DD222B7A4760 /* NSData+SHA.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AAC360BC9A1D1773D59C8E91186044E /* NSData+SHA.h */; settings = {ATTRIBUTES = (Public, ); }; }; B0981AE770653C8FE25311592E2BCE5E /* SwiftyRSA.h in Headers */ = {isa = PBXBuildFile; fileRef = 1CB44E2E8BE22C702325440A0F96410A /* SwiftyRSA.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -90,6 +91,7 @@ 38E84F551B29670A456D12366AA4C8E5 /* SwiftyRSA.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyRSA.swift; sourceTree = ""; }; 47807FD4AF81104758D4EA9B778F5186 /* multiple-keys-testcase.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "multiple-keys-testcase.sh"; sourceTree = ""; }; 5AAC360BC9A1D1773D59C8E91186044E /* NSData+SHA.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+SHA.h"; sourceTree = ""; }; + 6C24D5A0214B2FD7004C13F3 /* swiftyrsa-public.pub */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "swiftyrsa-public.pub"; sourceTree = ""; }; 6E2D7A1B9C4D58D0887FB023C0E15594 /* swiftyrsa-private-headerless.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "swiftyrsa-private-headerless.pem"; sourceTree = ""; }; 7DF0A3222330CDFCF12918ED08519EB9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 986B7C763D1B85C628621E89E7071B06 /* swiftyrsa-public.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "swiftyrsa-public.der"; sourceTree = ""; }; @@ -230,6 +232,7 @@ D240C55056247A7B270C92143EF30532 /* keys */ = { isa = PBXGroup; children = ( + 6C24D5A0214B2FD7004C13F3 /* swiftyrsa-public.pub */, C04D33801DC54D5700D65B05 /* swiftyrsa-public-base64-newlines.txt */, C065ED6E1DBF314500674763 /* swiftyrsa-private.der */, C096A0111D9085C100E285C2 /* swiftyrsa-public-base64.txt */, @@ -451,6 +454,7 @@ C04D33811DC54D5700D65B05 /* swiftyrsa-public-base64-newlines.txt in Resources */, C084A72A1EC3034B003F79ED /* swiftyrsa-private-header-octetstring.pem in Resources */, C076F5541DADEC1D006AF5DB /* swiftyrsa-public.der in Resources */, + 6C24D5A1214B2FD7004C13F3 /* swiftyrsa-public.pub in Resources */, C076F5551DADEC1D006AF5DB /* swiftyrsa-public.pem in Resources */, C065ED6F1DBF314500674763 /* swiftyrsa-private.der in Resources */, ); diff --git a/SwiftyRSA/PublicKey.swift b/SwiftyRSA/PublicKey.swift index a5dd626..ee4f399 100644 --- a/SwiftyRSA/PublicKey.swift +++ b/SwiftyRSA/PublicKey.swift @@ -31,6 +31,65 @@ public class PublicKey: Key { return pem } + /// Returns an OpenSSH representation of the public key. + /// + /// - Returns: Data of the key, OpenSSH encoded + /// - Throws: SwiftyRSAError + public func sshString() throws -> String{ + let data = try self.data() + let node = try! Asn1Parser.parse(data: data) + + // Ensure the raw data is an ASN1 sequence + guard case .sequence(let nodes) = node else { + throw SwiftyRSAError.invalidAsn1Structure + } + + let RSA_HEADER = "ssh-rsa" + + var ssh:String = RSA_HEADER + " " + var rsaBytes:Data = Data() + + // Get size of the header + var byteCount: UInt32 = UInt32(RSA_HEADER.count).bigEndian + var sizeData = Data(bytes: &byteCount, count: MemoryLayout.size(ofValue: byteCount)) + + // Append size of header and content of header + rsaBytes.append(sizeData) + rsaBytes += RSA_HEADER.data(using: .utf8)! + + // Get the exponent + if let exp = nodes.last, case .integer(let exponent) = exp { + // Get size of exponent + byteCount = UInt32(exponent.count).bigEndian + sizeData = Data(bytes: &byteCount, count: MemoryLayout.size(ofValue: byteCount)) + + // Append size of exponent and content of exponent + rsaBytes.append(sizeData) + rsaBytes += exponent + } + else{ + throw SwiftyRSAError.invalidAsn1Structure + } + + // Get the modulus + if let mod = nodes.first, case .integer(let modulus) = mod { + // Get size of modulus + byteCount = UInt32(modulus.count).bigEndian + sizeData = Data(bytes: &byteCount, count: MemoryLayout.size(ofValue: byteCount)) + + // Append size of modulus and content of modulus + rsaBytes.append(sizeData) + rsaBytes += modulus + } + else{ + throw SwiftyRSAError.invalidAsn1Structure + } + + ssh += rsaBytes.base64EncodedString() + "\n" + return ssh + + } + /// Creates a public key with a keychain key reference. /// This initializer will throw if the provided key reference is not a public RSA key. /// diff --git a/SwiftyRSATests/KeyTests.swift b/SwiftyRSATests/KeyTests.swift index fe31094..8e58e92 100644 --- a/SwiftyRSATests/KeyTests.swift +++ b/SwiftyRSATests/KeyTests.swift @@ -160,6 +160,18 @@ class PublicKeyTests: XCTestCase { XCTAssertNotNil(newPublicKey) XCTAssertEqual(try? publicKey.data(), try? newPublicKey.data()) } + + func test_sshString() throws { + let publicKey = try PublicKey(pemNamed: "swiftyrsa-public", in: bundle) + let sshString = try publicKey.sshString() + + guard let path = bundle.path(forResource: "swiftyrsa-public", ofType: "pub") else { + return XCTFail() + } + + let expectedKeyString = try String(contentsOf: URL(fileURLWithPath: path), encoding: .utf8) + XCTAssertEqual(sshString, expectedKeyString) + } } class PrivateKeyTests: XCTestCase { diff --git a/SwiftyRSATests/keys/swiftyrsa-public.pub b/SwiftyRSATests/keys/swiftyrsa-public.pub new file mode 100644 index 0000000..95ac52e --- /dev/null +++ b/SwiftyRSATests/keys/swiftyrsa-public.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDQZ0YVolLtPXXSowc6CopEXzGI/Tvri6hYT3KZ45G97DQn8ocydBQXSZfRR92MpiZHQn1zydpVBOCj7tUnSh1QoSN9aISG+tuLggYWdav6XlW2JAlduHkMbbyug9aoWIyaZjXXzyV+0e3hjwQhfTeQj9DLuGssWNX3YuYgf/e5LQ==