Skip to content

Commit

Permalink
feat: Add PS256, PS384 and PS512 support (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew-Lees11 authored and djones6 committed May 14, 2019
1 parent a2a88e9 commit 2783f00
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 5 additions & 3 deletions Sources/SwiftJWT/BlueRSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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)")
Expand Down
19 changes: 19 additions & 0 deletions Sources/SwiftJWT/JWTSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
19 changes: 19 additions & 0 deletions Sources/SwiftJWT/JWTVerifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
61 changes: 60 additions & 1 deletion Tests/SwiftJWTTests/TestJWT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
Expand All @@ -128,6 +132,7 @@ class TestJWT: XCTestCase {
("testJWTDecoderKeyID", testJWTDecoderKeyID),
("testJWTCoderCycleKeyID", testJWTCoderCycleKeyID),
("testJWT", testJWT),
("testJWTRSAPSS", testJWTRSAPSS),
("testJWTUsingHMAC", testJWTUsingHMAC),
("testJWTUsingECDSA", testJWTUsingECDSA),
("testMicroProfile", testMicroProfile),
Expand All @@ -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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -471,6 +506,30 @@ class TestJWT: XCTestCase {
}
}

// From jwt.io
func testJWTRSAPSS() {
if #available(OSX 10.13, *) {
let ok = JWT<TestClaims>.verify(rsaPSSEncodedTestClaimJWT, using: .ps256(publicKey: rsaPublicKey))
XCTAssertTrue(ok, "Verification failed")

if let decoded = try? JWT<TestClaims>(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")
Expand Down
36 changes: 25 additions & 11 deletions Tests/SwiftJWTTests/rsa_private_key
Original file line number Diff line number Diff line change
@@ -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-----
13 changes: 8 additions & 5 deletions Tests/SwiftJWTTests/rsa_public_key
Original file line number Diff line number Diff line change
@@ -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-----

0 comments on commit 2783f00

Please sign in to comment.