diff --git a/Package.swift b/Package.swift index 6c1205e..9f417a6 100644 --- a/Package.swift +++ b/Package.swift @@ -28,7 +28,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/IBM-Swift/BlueRSA.git", from: "1.0.24"), + .package(url: "https://github.com/IBM-Swift/BlueRSA.git", from: "1.0.31"), .package(url: "https://github.com/IBM-Swift/BlueCryptor.git", from: "1.0.0"), .package(url: "https://github.com/IBM-Swift/BlueECC.git", from: "1.1.0"), .package(url: "https://github.com/IBM-Swift/LoggerAPI.git", from: "1.7.0"), diff --git a/README.md b/README.md index 4a6cdfc..665ad78 100644 --- a/README.md +++ b/README.md @@ -179,9 +179,12 @@ The supported algorithms for signing and verifying JWTs are: * ES256 - ECDSA using using SHA-256 and a P-256 curve * ES384 - ECDSA using using SHA-384 and a P-384 curve * ES512 - ECDSA using using SHA-512 and a P-521 curve +* PS256 - RSA-PSS using SHA-256 +* PS384 - RSA-PSS using SHA-384 +* PS512 - RSA-PSS using SHA-512 * none - Don't sign or verify the JWT -Note: ECDSA algorithms require a minimum Swift version of 4.1. +Note: ECDSA and RSA-PSS algorithms require a minimum Swift version of 4.1. ### Validate claims diff --git a/Sources/SwiftJWT/BlueRSA.swift b/Sources/SwiftJWT/BlueRSA.swift index dc202f2..7dee825 100644 --- a/Sources/SwiftJWT/BlueRSA.swift +++ b/Sources/SwiftJWT/BlueRSA.swift @@ -25,11 +25,13 @@ class BlueRSA: SignerAlgorithm, VerifierAlgorithm { private let key: Data private let keyType: RSAKeyType private let algorithm: Data.Algorithm + private let usePSS: Bool - init(key: Data, keyType: RSAKeyType?=nil, algorithm: Data.Algorithm) { + init(key: Data, keyType: RSAKeyType?=nil, algorithm: Data.Algorithm, usePSS: Bool = false) { self.key = key self.keyType = keyType ?? .publicKey self.algorithm = algorithm + self.usePSS = usePSS } func sign(header: String, claims: String) throws -> String { @@ -52,7 +54,7 @@ class BlueRSA: SignerAlgorithm, VerifierAlgorithm { } let privateKey = try CryptorRSA.createPrivateKey(withPEM: keyString) let myPlaintext = CryptorRSA.createPlaintext(with: data) - guard let signedData = try myPlaintext.signed(with: privateKey, algorithm: algorithm) else { + guard let signedData = try myPlaintext.signed(with: privateKey, algorithm: algorithm, usePSS: usePSS) else { throw JWTError.invalidPrivateKey } return signedData.data @@ -92,7 +94,7 @@ class BlueRSA: SignerAlgorithm, VerifierAlgorithm { } let myPlaintext = CryptorRSA.createPlaintext(with: data) let signedData = CryptorRSA.createSigned(with: signature) - return try myPlaintext.verify(with: publicKey, signature: signedData, algorithm: algorithm) + return try myPlaintext.verify(with: publicKey, signature: signedData, algorithm: algorithm, usePSS: usePSS) } catch { Log.error("Verification failed: \(error)") diff --git a/Sources/SwiftJWT/JWTSigner.swift b/Sources/SwiftJWT/JWTSigner.swift index cc1ac0d..753cfed 100644 --- a/Sources/SwiftJWT/JWTSigner.swift +++ b/Sources/SwiftJWT/JWTSigner.swift @@ -83,6 +83,25 @@ public struct JWTSigner { return JWTSigner(name: "RS512", signerAlgorithm: BlueRSA(key: privateKey, keyType: .privateKey, algorithm: .sha512)) } + /// Initialize a JWTSigner using the RSA-PSS 256 bits algorithm and the provided privateKey. + /// - Parameter privateKey: The UTF8 encoded PEM private key, with a "BEGIN RSA PRIVATE KEY" header. + public static func ps256(privateKey: Data) -> JWTSigner { + return JWTSigner(name: "PS256", signerAlgorithm: BlueRSA(key: privateKey, keyType: .privateKey, algorithm: .sha256, usePSS: true)) + } + + /// Initialize a JWTSigner using the RSA-PSS 384 bits algorithm and the provided privateKey. + /// - Parameter privateKey: The UTF8 encoded PEM private key, with a "BEGIN RSA PRIVATE KEY" header. + public static func ps384(privateKey: Data) -> JWTSigner { + return JWTSigner(name: "PS384", signerAlgorithm: BlueRSA(key: privateKey, keyType: .privateKey, algorithm: .sha384, usePSS: true)) + } + + /// Initialize a JWTSigner using the RSA-PSS 512 bits algorithm and the provided privateKey. + /// This signer requires at least a 2048 bit RSA key. + /// - Parameter privateKey: The UTF8 encoded PEM private key, with a "BEGIN RSA PRIVATE KEY" header. + public static func ps512(privateKey: Data) -> JWTSigner { + return JWTSigner(name: "PS512", signerAlgorithm: BlueRSA(key: privateKey, keyType: .privateKey, algorithm: .sha512, usePSS: true)) + } + /// Initialize a JWTSigner using the HMAC 256 bits algorithm and the provided privateKey. /// - Parameter key: The HMAC symmetric password data. public static func hs256(key: Data) -> JWTSigner { diff --git a/Sources/SwiftJWT/JWTVerifier.swift b/Sources/SwiftJWT/JWTVerifier.swift index 3b685b1..99ed054 100644 --- a/Sources/SwiftJWT/JWTVerifier.swift +++ b/Sources/SwiftJWT/JWTVerifier.swift @@ -89,6 +89,25 @@ public struct JWTVerifier { return JWTVerifier(verifierAlgorithm: BlueRSA(key: certificate, keyType: .certificate, algorithm: .sha512)) } + /// Initialize a JWTVerifier using the RSA-PSS 256 bits algorithm and the provided publicKey. + /// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header. + public static func ps256(publicKey: Data) -> JWTVerifier { + return JWTVerifier(verifierAlgorithm: BlueRSA(key: publicKey, keyType: .publicKey, algorithm: .sha256, usePSS: true)) + } + + /// Initialize a JWTVerifier using the RSA-PSS 384 bits algorithm and the provided publicKey. + /// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header. + public static func ps384(publicKey: Data) -> JWTVerifier { + return JWTVerifier(verifierAlgorithm: BlueRSA(key: publicKey, keyType: .publicKey, algorithm: .sha384, usePSS: true)) + } + + /// Initialize a JWTVerifier using the RSA-PSS 512 bits algorithm and the provided publicKey. + /// This verifier requires at least a 2048 bit RSA key. + /// - Parameter publicKey: The UTF8 encoded PEM public key, with a "BEGIN PUBLIC KEY" header. + public static func ps512(publicKey: Data) -> JWTVerifier { + return JWTVerifier(verifierAlgorithm: BlueRSA(key: publicKey, keyType: .publicKey, algorithm: .sha512, usePSS: true)) + } + /// Initialize a JWTSigner using the HMAC 256 bits algorithm and the provided privateKey. /// - Parameter key: The HMAC symmetric password data. public static func hs256(key: Data) -> JWTVerifier { diff --git a/Tests/SwiftJWTTests/TestJWT.swift b/Tests/SwiftJWTTests/TestJWT.swift index b1852d4..57e6f5e 100644 --- a/Tests/SwiftJWTTests/TestJWT.swift +++ b/Tests/SwiftJWTTests/TestJWT.swift @@ -31,7 +31,8 @@ let rsaJWTEncoder = JWTEncoder(jwtSigner: .rs256(privateKey: rsaPrivateKey)) let rsaJWTDecoder = JWTDecoder(jwtVerifier: .rs256(publicKey: rsaPublicKey)) let certPrivateKey = read(fileName: "cert_private_key") let certificate = read(fileName: "certificate") -let rsaEncodedTestClaimJWT = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwic3ViIjoiMTIzNDU2Nzg5MCIsImlhdCI6MTUxNjIzOTAyMn0.HbPVSMBtR3l0zyrHIlGRyXkNECgE0RrQreebA2xuIWhN-64MP29-lf8lg5pWKk3gTrnbOxEpek5AvBNgz4VK34enkzhrrMKonBywvZZ8CQtM5FlArgx5ZQqxjD32B7WCqlDOelly1W2rlFNIopBit-OuKBw1ioxQwzDMLb1Ol3Q" +let rsaEncodedTestClaimJWT = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.pOeiYYHuxBu27llpKrLfHX-tt0Cr41m3hn7d1_CPl7dRMksQRJC5U7AM2CkF8uyObwAKg88orK6eHlOQ0x2C4gDoG7WmgszpthOB6ZUTUPj_FNsn3z4fM8sFx3wON7jtRRSuULH13f-RjLoIFhY_VuqVhla3ybjnfbwjcsd8EqDumdFN6La5D0KugCgvuH51JaEjdHfwXkxkRsynmhv3jCpvRbUbforfEnDjyAImez2hd0Pnb3Vtqr-21z1vFWqqRiz_K-qSiO5NTaO1VbLg7SOYBB9hMAD-_6R2ZZh0JvFP7hycCftRIxTSDd5r0I9sQh9iqurVq03_h0ZjS9BwJQ" +let rsaPSSEncodedTestClaimJWT = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.gytxKF6HsfRI-dmyS0KsVoGquSWHTbosqmgBM_8UnTBZd56ThVdxZSTZzkVE0kS6SZdN7iogLiPqWzvGac0orTkf3XMbxecpHiO8b_BywIqrXhXcz84NFxrq5s8KL_LB8Cs9ro_5xmptp_fNtCedg9leju7VUzrEZP0hyTG_dlar2t7SWY47JD3rlgdaXEfkGgSuDgOO2CzBzqlbP7DUxTQ6OwI8RMNWAxeCalvWTgNQkb1DAy_2JKaga4zDnieUHMd2c_8iSt6SS9dLkxl4nih_2IjHJc73Qcg4Epx5CrhJXWg5CmKoTFDVgpMBGXJRIxT5qpAAx4qvRzlNgJVM2A" let certificateEncodedTestClaimJWT = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwic3ViIjoiMTIzNDU2Nzg5MCJ9.CpnzQLuWGfH5Kba36vg0ZZKBnzwlrIgapFVfBfk_nea-eej84ktHZANqIeolskZopRJ4DQ3oaLtHWEg16-ZsujxmkOdiAIbk0-C4QLOVFLZH78WLZAqkyNLS8rFuK9hloLNwz1j6VVUd1f0SOT-wIRzL0_0VRYqQd1bVcCj7wc7BmXENlOfHY7KGHS-6JX-EClT1DygDSoCmdvBExBf3vx0lwMIbP4ryKkyhOoU13ZfSUt1gpP9nZAfzqfRTPxZc_f7neiAlMlF6SzsedsskRCNegW8cg5e_NuVmZZkj0_bnswXFDMmIaxiPdtOEWkmyEOca-EHSwbO5PgCgXOIrgg" // A `TestClaims` encoded using HMAC with "Super Secret Key" from "www.jwt.io" let hmacEncodedTestClaimJWT = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwic3ViIjoiMTIzNDU2Nzg5MCJ9.8kIE0ZCq1Vw7aW1kACpgJLcgY2DpTXgO6P5T3cdCuTs" @@ -110,14 +111,17 @@ class TestJWT: XCTestCase { return [ ("testSignAndVerify", testSignAndVerify), ("testSignAndVerifyRSA", testSignAndVerifyRSA), + ("testSignAndVerifyRSAPSS", testSignAndVerifyRSAPSS), ("testSignAndVerifyCert", testSignAndVerifyCert), ("testSignAndVerifyHMAC", testSignAndVerifyHMAC), ("testSignAndVerifyECDSA", testSignAndVerifyECDSA), ("testSignAndVerifyRSA384", testSignAndVerifyRSA384), + ("testSignAndVerifyRSAPSS384", testSignAndVerifyRSAPSS384), ("testSignAndVerifyCert384", testSignAndVerifyCert384), ("testSignAndVerifyHMAC384", testSignAndVerifyHMAC384), ("testSignAndVerifyECDSA384", testSignAndVerifyECDSA384), ("testSignAndVerifyRSA512", testSignAndVerifyRSA512), + ("testSignAndVerifyRSAPSS512", testSignAndVerifyRSAPSS512), ("testSignAndVerifyCert512", testSignAndVerifyCert512), ("testSignAndVerifyHMAC512", testSignAndVerifyHMAC512), ("testSignAndVerifyECDSA512", testSignAndVerifyECDSA512), @@ -128,6 +132,7 @@ class TestJWT: XCTestCase { ("testJWTDecoderKeyID", testJWTDecoderKeyID), ("testJWTCoderCycleKeyID", testJWTCoderCycleKeyID), ("testJWT", testJWT), + ("testJWTRSAPSS", testJWTRSAPSS), ("testJWTUsingHMAC", testJWTUsingHMAC), ("testJWTUsingECDSA", testJWTUsingECDSA), ("testMicroProfile", testMicroProfile), @@ -153,6 +158,16 @@ class TestJWT: XCTestCase { } } + func testSignAndVerifyRSAPSS() { + if #available(OSX 10.13, *) { + do { + try signAndVerify(signer: .ps256(privateKey: rsaPrivateKey), verifier: .ps256(publicKey: rsaPublicKey)) + } catch { + XCTFail("testSignAndVerify failed: \(error)") + } + } + } + func testSignAndVerifyCert() { do { try signAndVerify(signer: .rs256(privateKey: certPrivateKey), verifier: .rs256(certificate: certificate)) @@ -188,6 +203,16 @@ class TestJWT: XCTestCase { } } + func testSignAndVerifyRSAPSS384() { + if #available(OSX 10.13, *) { + do { + try signAndVerify(signer: .ps384(privateKey: rsaPrivateKey), verifier: .ps384(publicKey: rsaPublicKey)) + } catch { + XCTFail("testSignAndVerify failed: \(error)") + } + } + } + func testSignAndVerifyCert384() { do { try signAndVerify(signer: .rs384(privateKey: certPrivateKey), verifier: .rs384(certificate: certificate)) @@ -223,6 +248,16 @@ class TestJWT: XCTestCase { } } + func testSignAndVerifyRSAPSS512() { + if #available(OSX 10.13, *) { + do { + try signAndVerify(signer: .ps512(privateKey: rsaPrivateKey), verifier: .ps512(publicKey: rsaPublicKey)) + } catch { + XCTFail("testSignAndVerify failed: \(error)") + } + } + } + func testSignAndVerifyCert512() { do { try signAndVerify(signer: .rs512(privateKey: certPrivateKey), verifier: .rs512(certificate: certificate)) @@ -471,6 +506,30 @@ class TestJWT: XCTestCase { } } + // From jwt.io + func testJWTRSAPSS() { + if #available(OSX 10.13, *) { + let ok = JWT.verify(rsaPSSEncodedTestClaimJWT, using: .ps256(publicKey: rsaPublicKey)) + XCTAssertTrue(ok, "Verification failed") + + if let decoded = try? JWT(jwtString: rsaPSSEncodedTestClaimJWT) { + XCTAssertEqual(decoded.header.alg, "PS256", "Wrong .alg in decoded") + XCTAssertEqual(decoded.header.typ, "JWT", "Wrong .typ in decoded") + + XCTAssertEqual(decoded.claims.sub, "1234567890", "Wrong .sub in decoded") + XCTAssertEqual(decoded.claims.name, "John Doe", "Wrong .name in decoded") + XCTAssertEqual(decoded.claims.admin, true, "Wrong .admin in decoded") + XCTAssertEqual(decoded.claims.iat, Date(timeIntervalSince1970: 1516239022), "Wrong .iat in decoded") + + + XCTAssertEqual(decoded.validateClaims(), .success, "Validation failed") + } + else { + XCTFail("Failed to decode") + } + } + } + func testJWTUsingHMAC() { guard let hmacData = "Super Secret Key".data(using: .utf8) else { return XCTFail("Failed to convert hmacKey to Data") diff --git a/Tests/SwiftJWTTests/rsa_private_key b/Tests/SwiftJWTTests/rsa_private_key index b516a64..17e105d 100644 --- a/Tests/SwiftJWTTests/rsa_private_key +++ b/Tests/SwiftJWTTests/rsa_private_key @@ -1,13 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp -wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5 -1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh -3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2 -pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX -GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il -AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF -L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k -X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl -U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ -37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0= +MIIEpgIBAAKCAQEArY09Owko87BYqQKBYeUHjq5mlvL23cPPxmLKzEoo16FD3ud30 +VsuabM6FGds6YrULfBQnJrAkuLHrZtFVDQPNKsZ0Ui6721c5EEw0GDJ+NXgbej+g6 +gAsJ7xedmLPLIHO40QSV64mYjTh/73TZzF/dXJaGWSAwan+fsRls0Ltn2xdw8htgA +IKzH0zrEMBVFpW7RroTx5qdZAQVNB7b5S/iucBDy5pvMAre3tJ30mhpt0tZtLOL7t +pphhjf4FRnHS/VHb8wN6mrUu4r+7v2ZpfzJwQbkDBjxzyCl0iDGQoQo0dMUIuE0yu +i9ur9+9L5gEREuDPMRXNPIwh/dgMEWo7QIDAQABAoIBAQCb0SsgTeEOqU1wsGcpVU ++rsrk43Xr3ME7jdt9M+2gf8RHWl0rkFFFfZSLIHvOR2qsVc6VsdSRgvGs6WyWrq/H +a/6N2Wy46uJ2l8UG6VKwBVUTiaUXZPoUgDhQPqllwbQZBWS0+MmTatBX3C9tNO2wn +skHaERc+0EMQFXJ9Sisx87yA8RlhhuEaNXqxaQzTohHSvrpoHMqkjx0RsFD9py414 +KlYRKCiMyI8xkWa9FkvyYB+wwmmQwRcoxBgucpyuOWunGdqF/Hxs8b6d9qWJ66d2e +N1F9JCSS7cAH1CIRTxkZTH4qrxE5iMl8LuB3Xe1Qw7s60BPmmuGB4HmLKtwQShAoG +BAOFr5MEmhyuY1EdmCaxlEYLakBu4xux+WBXQ533VgHrSO6Hlk1b36AEzV0hIBWEW +8BViSQv0SOyDGPG4EhfLe6KPA2dz7ZwTl+WrZtalDYJGRytSCyI9/cNh6s1adG8zO +MW+Na7sq5lqXMCGnHeN6rb/zO6aiModQBHEIVUicTcHAoGBAMUYFs5T88kuD3swLo +itToe0wdKlN4ytPi9O+8hZknn2wiP81rvunhrNWwK0ixIYrUkVR/o9sDBoNTVGZ+2 +0Ts3cmTFTr0/9L+eFzj1YbogtXHQaaDLhiLeMAynmSe0ItlxCafTgNUZVN3JN1XD5 +ByiCRqTVAAUM1BK5J/eao89rAoGBAL05+u8AzpsUUa4Vw60Jsmdr3WjH9YR0krDNi +mWhIZb4f8JpmwN4WzMWzAALJSjZPnRU6wiz6btQWVIfuGBtGb3d23euYxmPgQT0mw +E30F05bWi6GEqIS0sGGTqE4hSupMup3hqW5X2FQZMD7LiXUx6HIJpy8rznTO8c+vY +iUXNlAoGBAKRyMe0bPOfZp48YJ2tgdoph9eokcdHNZnogg5Gpsr5Sda+DMUOCl6Yz +O0FplDOYJVU6DWEsgUoSWHrH8MTzUWEQMz1l3nt1+7dH/ElQ9IBooKA6vD/fz6udh +bI68+lzAHy/6RoozCqPxYB4kqL9FMzmzbyP/8E27+djV4aPHyhbAoGBAKAKeLgkmn +slJA1JlN28MfSdPhxe83wowB/w9mjpl+IDwY4X27sd9M//uWCKm/i5Sp88JJtNrgs +4Uu0GZ1PGb1/3A0Kj0ZZ/2u8f5OpsZl2x05w8inkqXgWsXd5XOVfAXoDiLoouMlkP +sWBKUgg4LGfIA7Jnzi38sonojtb2Iz+y -----END RSA PRIVATE KEY----- diff --git a/Tests/SwiftJWTTests/rsa_public_key b/Tests/SwiftJWTTests/rsa_public_key index 8a3e20e..6854076 100644 --- a/Tests/SwiftJWTTests/rsa_public_key +++ b/Tests/SwiftJWTTests/rsa_public_key @@ -1,5 +1,8 @@ ------BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0 -FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/ -3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB ------END PUBLIC KEY----- +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEArY09Owko87BYqQKBYeUHjq5mlvL23cPPxmLKzEoo16FD3ud30Vsua +bM6FGds6YrULfBQnJrAkuLHrZtFVDQPNKsZ0Ui6721c5EEw0GDJ+NXgbej+g6gAsJ +7xedmLPLIHO40QSV64mYjTh/73TZzF/dXJaGWSAwan+fsRls0Ltn2xdw8htgAIKzH +0zrEMBVFpW7RroTx5qdZAQVNB7b5S/iucBDy5pvMAre3tJ30mhpt0tZtLOL7tpphh +jf4FRnHS/VHb8wN6mrUu4r+7v2ZpfzJwQbkDBjxzyCl0iDGQoQo0dMUIuE0yui9ur +9+9L5gEREuDPMRXNPIwh/dgMEWo7QIDAQAB +-----END RSA PUBLIC KEY-----