Skip to content

Commit

Permalink
Add RSAKey support for x509 certificate (#42)
Browse files Browse the repository at this point in the history
* add rsa support for x509 certificate

* add firebase test
  • Loading branch information
tanner0101 authored Aug 20, 2020
1 parent 3e631a2 commit 50a46ba
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 8 deletions.
104 changes: 104 additions & 0 deletions Sources/JWTKit/RSA/RSAKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,38 @@ import CJWTKitBoringSSL
import struct Foundation.Data

public final class RSAKey: OpenSSLKey {
/// Creates RSAKey from public key pem file.
///
/// Public key pem files look like:
///
/// -----BEGIN PUBLIC KEY-----
/// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
/// ...
/// aX4rbSL49Z3dAQn8vQIDAQAB
/// -----END PUBLIC KEY-----
///
/// This key can only be used to verify JWTs.
///
/// - parameters:
/// - pem: Contents of pem file.
public static func `public`(pem string: String) throws -> RSAKey {
try .public(pem: [UInt8](string.utf8))
}

/// Creates RSAKey from public key pem file.
///
/// Public key pem files look like:
///
/// -----BEGIN PUBLIC KEY-----
/// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
/// ...
/// aX4rbSL49Z3dAQn8vQIDAQAB
/// -----END PUBLIC KEY-----
///
/// This key can only be used to verify JWTs.
///
/// - parameters:
/// - pem: Contents of pem file.
public static func `public`<Data>(pem data: Data) throws -> RSAKey
where Data: DataProtocol
{
Expand All @@ -20,10 +48,86 @@ public final class RSAKey: OpenSSLKey {
return self.init(c, .public)
}

/// Creates RSAKey from public certificate pem file.
///
/// Certificate pem files look like:
///
/// -----BEGIN CERTIFICATE-----
/// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
/// ...
/// aX4rbSL49Z3dAQn8vQIDAQAB
/// -----END CERTIFICATE-----
///
/// This key can only be used to verify JWTs.
///
/// - parameters:
/// - pem: Contents of pem file.
public static func certificate(pem string: String) throws -> RSAKey {
try self.certificate(pem: [UInt8](string.utf8))
}

/// Creates RSAKey from public certificate pem file.
///
/// Certificate pem files look like:
///
/// -----BEGIN CERTIFICATE-----
/// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
/// ...
/// aX4rbSL49Z3dAQn8vQIDAQAB
/// -----END CERTIFICATE-----
///
/// This key can only be used to verify JWTs.
///
/// - parameters:
/// - pem: Contents of pem file.
public static func certificate<Data>(pem data: Data) throws -> RSAKey
where Data: DataProtocol
{
let x509 = try self.load(pem: data) { bio in
CJWTKitBoringSSL_PEM_read_bio_X509(bio, nil, nil, nil)
}
defer { CJWTKitBoringSSL_X509_free(x509) }
let pkey = CJWTKitBoringSSL_X509_get_pubkey(x509)
defer { CJWTKitBoringSSL_EVP_PKEY_free(pkey) }

guard let c = CJWTKitBoringSSL_EVP_PKEY_get1_RSA(pkey) else {
throw JWTError.signingAlgorithmFailure(RSAError.keyInitializationFailure)
}
return self.init(c, .public)
}

/// Creates RSAKey from private key pem file.
///
/// Private key pem files look like:
///
/// -----BEGIN PRIVATE KEY-----
/// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
/// ...
/// aX4rbSL49Z3dAQn8vQIDAQAB
/// -----END PRIVATE KEY-----
///
/// This key can be used to verify and sign JWTs.
///
/// - parameters:
/// - pem: Contents of pem file.
public static func `private`(pem string: String) throws -> RSAKey {
try .private(pem: [UInt8](string.utf8))
}

/// Creates RSAKey from private key pem file.
///
/// Private key pem files look like:
///
/// -----BEGIN PRIVATE KEY-----
/// MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
/// ...
/// aX4rbSL49Z3dAQn8vQIDAQAB
/// -----END PRIVATE KEY-----
///
/// This key can be used to verify and sign JWTs.
///
/// - parameters:
/// - pem: Contents of pem file.
public static func `private`<Data>(pem data: Data) throws -> RSAKey
where Data: DataProtocol
{
Expand Down
11 changes: 3 additions & 8 deletions Sources/JWTKit/Utilities/OpenSSLSigner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,10 @@ extension OpenSSLKey {
static func load<Data, T>(pem data: Data, _ closure: (UnsafeMutablePointer<BIO>) -> (T?)) throws -> T
where Data: DataProtocol
{
let bio = CJWTKitBoringSSL_BIO_new(CJWTKitBoringSSL_BIO_s_mem())
let bytes = data.copyBytes()
let bio = CJWTKitBoringSSL_BIO_new_mem_buf(bytes, numericCast(bytes.count))
defer { CJWTKitBoringSSL_BIO_free(bio) }

guard (data.copyBytes() + [0]).withUnsafeBytes({ pointer in
CJWTKitBoringSSL_BIO_puts(bio, pointer.baseAddress?.assumingMemoryBound(to: Int8.self))
}) >= 0 else {
throw JWTError.signingAlgorithmFailure(OpenSSLError.bioPutsFailure)
}


guard let bioPtr = bio, let c = closure(bioPtr) else {
throw JWTError.signingAlgorithmFailure(OpenSSLError.bioConversionFailure)
}
Expand Down
102 changes: 102 additions & 0 deletions Tests/JWTKitTests/JWTKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,29 @@ class JWTKitTests: XCTestCase {
let signers = JWTSigners()
try signers.use(jwksJSON: microsoftJWKS)
}

func testRSACertificate() throws {
let test = TestPayload(
sub: "vapor",
name: "foo",
admin: true,
exp: .init(value: .distantFuture)
)
let jwt = try JWTSigner.rs256(
key: .private(pem: rsa2PrivateKey)
).sign(test)

let payload = try JWTSigner.rs256(
key: .certificate(pem: rsa2Cert)
).verify(jwt, as: TestPayload.self)
XCTAssertEqual(payload, test)
}

func testFirebaseJWTAndCertificate() throws {
let payload = try JWTSigner.rs256(key: .certificate(pem: firebaseCert))
.verify(firebaseJWT, as: FirebasePayload.self)
XCTAssertEqual(payload.userID, "y8wiKThXGKM88xxrQWDZzKnBuqv2")
}
}

struct AudiencePayload: Codable {
Expand Down Expand Up @@ -611,6 +634,85 @@ aX4rbSL49Z3dAQn8vQIDAQAB
-----END PUBLIC KEY-----
"""

let rsa2PrivateKey = """
-----BEGIN PRIVATE KEY-----
MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAtgeOpWeiRIq0Blbc
qq4P7sKnyDmj1mpQq7OyRKZM0qbwyyMM5Nisf5Y+RSDM7JDwqMeLspGo5znLBzN5
L14JIQIDAQABAkBlMWRSfX9O3VDhKU65L9S5pcsCW1DCdQ3tthMHaO/SNn4jhmbf
MamrK4TWctjuau+CwUtQz/kS/fjveYBSVklVAiEA2r1fExLdTwo1pRzCqvUhq7MO
4wu1dPvv8mJZZvGxQGMCIQDVCVsmeiN+s9erwd95wUZKb4zBkT6MQC0r1fGQBnEN
qwIgBBT6nDmC5cG0BJPH0jbm3PRnd7c1OKym6qgJMRGblC8CICh9Zr2haS2jsNIM
PxU9DscG/JGtsV2mtO8n8omVL9eRAiEA1ccs/gJCMAwJ/jeA8tZwOF3GEb/9tGow
RR8+JsDsJY8=
-----END PRIVATE KEY-----
"""

let rsa2Cert = """
-----BEGIN CERTIFICATE-----
MIIBzjCCAXgCCQDnzO/FvcHZbjANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCTlkxDDAKBgNVBAcMA05ZQzEOMAwGA1UECgwFVmFwb3IxFDAS
BgNVBAMMC3ZhcG9yLmNvZGVzMR4wHAYJKoZIhvcNAQkBFg9qd3RAdmFwb3IuY29k
ZXMwHhcNMjAwNzMxMjMyOTQ5WhcNMjEwNzMxMjMyOTQ5WjBuMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCTlkxDDAKBgNVBAcMA05ZQzEOMAwGA1UECgwFVmFwb3IxFDAS
BgNVBAMMC3ZhcG9yLmNvZGVzMR4wHAYJKoZIhvcNAQkBFg9qd3RAdmFwb3IuY29k
ZXMwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAtgeOpWeiRIq0Blbcqq4P7sKnyDmj
1mpQq7OyRKZM0qbwyyMM5Nisf5Y+RSDM7JDwqMeLspGo5znLBzN5L14JIQIDAQAB
MA0GCSqGSIb3DQEBCwUAA0EAQyBP1X40S4joTg1ov4eK0aKNlRLbWftEorGh5jCc
F3IAwlztc7uFj589k/M+xO4TGdrEVlMyiVdC5/B0MLa8LQ==
-----END CERTIFICATE-----
"""

struct FirebasePayload: JWTPayload, Equatable {
enum CodingKeys: String, CodingKey {
case providerID = "provider_id"
case issuer = "iss"
case audience = "aud"
case authTime = "auth_time"
case userID = "user_id"
case subject = "sub"
case issuedAt = "iat"
case expiration = "exp"
}
let providerID: String
let issuer: IssuerClaim
let audience: AudienceClaim
let authTime: Int
let userID: String
let subject: SubjectClaim
let issuedAt: IssuedAtClaim
let expiration: ExpirationClaim

func verify(using signer: JWTSigner) throws {
try self.expiration.verifyNotExpired(currentDate: .distantPast)
}
}

let firebaseJWT = """
eyJhbGciOiJSUzI1NiIsImtpZCI6IjU1NGE3NTQ3Nzg1ODdjOTRjMTY3M2U4ZWEyNDQ2MTZjMGMwNDNjYmMiLCJ0eXAiOiJKV1QifQ.eyJwcm92aWRlcl9pZCI6ImFub255bW91cyIsImlzcyI6Imh0dHBzOi8vc2VjdXJldG9rZW4uZ29vZ2xlLmNvbS92enNnLXNjaGVkdWxlLXRlc3QiLCJhdWQiOiJ2enNnLXNjaGVkdWxlLXRlc3QiLCJhdXRoX3RpbWUiOjE1OTYyMzg5ODIsInVzZXJfaWQiOiJ5OHdpS1RoWEdLTTg4eHhyUVdEWnpLbkJ1cXYyIiwic3ViIjoieTh3aUtUaFhHS004OHh4clFXRFp6S25CdXF2MiIsImlhdCI6MTU5NjIzODk4MiwiZXhwIjoxNTk2MjQyNTgyLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7fSwic2lnbl9pbl9wcm92aWRlciI6ImFub255bW91cyJ9fQ.vW5N3RqN8ba_P56GgjyMY-RE3hr_ciEw-E_oBtVjMJw3pgIO7MDHj0eRqTDTbjapN0BhkxTjkOA-L5pGO-9uA7afO-45vmiyaFDaN_oIYHNCewDgVaphDy_CYQ1PJugZHVjumk-qgzdS9nen_6oXmWZ1CYMop-g8UEyVHUaU-yjnvYSvvRWcas--HaErcsPY6uDx9DR8R2_mC-_VHBD58zN1svjTELkeVIZtkvA2Pxy1WO1NKxc0hWiz7w6RTu6P56_DJ1OqyMwxQavblaufdjccuC3bnv_MGKM8xhtsYLFWPnwFD762A50cHyS6SondruP7UnFQc1owlB6gaxEihw
"""

let firebaseCert = """
-----BEGIN CERTIFICATE-----
MIIDHDCCAgSgAwIBAgIIOvZ+ZDrIgmQwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE
AxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMjAw
NzI0MDkyMDAxWhcNMjAwODA5MjEzNTAxWjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl
bi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANSBPQydBvIITxwMsm0adXL5ToKR6Aihi3fCepGZj1Oq2pdq
r9ObfFcDX4GKHF7w6pm8WXxoZnjO37waSJc1ECmZt11tR0Ei/f0huLqDqNItGWRc
ApogR3Af8C12IwFbxvp5tPj4s8H7Ldnrr97zzXogrTKvQCVJQJE43SfqcOO0T1br
gfskj+G863Uy5JN7S8OijDLFK3YGIIvQDv6jp0tVrRwUUedJ4qET3IVWLkW5jAcd
WAy7/RmIVVZFXuqjyunU6xNd6gLw5uZPZdLjSW9CccFmZQfinuNKyFGLhdF00TMq
Torq8EOjFanRbxRi3mb9g01hVKY8WcsK1CE4RCMCAwEAAaM4MDYwDAYDVR0TAQH/
BAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ
KoZIhvcNAQEFBQADggEBAGMRck+Afw3zQF3SqgJ80bCgFJy4CidQuoNuElA673Y+
H4ulR5n/UV3feelR2+q0PvbZIVNf3Y5Yt+AWK9uK3LPprouFnx4U2X+mxsLHlHUC
Kl+wKoLuDvAmiDHu5JIjoYO0el6JJYNVnG3wCrSLLc6ehA32hfngdtJmkDN0/OoM
xmbj7X3JWctiJw0NxmH8wrKbeZLVIsaCwfc8iKjwcqRyA6hUxTobcsNs3IZsYv2W
g/5ZupoI8k2foTq4OdXJH/hkq4N5AyLp9S/RSodW6X+gexxohtgJxGx0gojotMzX
sb7NLsl7DkvjjxTz7I98xaGbfhofgYympeKT6UO+tmc=
-----END CERTIFICATE-----
"""

extension String {
var bytes: [UInt8] {
return .init(self.utf8)
Expand Down

0 comments on commit 50a46ba

Please sign in to comment.