Skip to content

Commit

Permalink
Add option to fetch RSA primitives (#127)
Browse files Browse the repository at this point in the history
* Add option to fetch RSA primitives

* Typo

* Remove unused files

* Update NOTICES
  • Loading branch information
ptoffy committed Nov 25, 2023
1 parent 6594257 commit 7ceeaa7
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 122 deletions.
10 changes: 8 additions & 2 deletions NOTICES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@
//
//===----------------------------------------------------------------------===//

This product contains a derivation of the Vendor BoringSSL and Build ASM scripts
from Swift Crypto.
This product contains a derivation of the Wycheproof tests from the Swift Crypto package.

* LICENSE (Apache License 2.0):
* https://www.apache.org/licenses/LICENSE-2.0
* HOMEPAGE:
* https://github.com/apple/swift-crypto

This product contains the SubjectPublicKeyInfo.swift file from the Swift ASN1 package.

* LICENSE (Apache License 2.0):
* https://www.apache.org/licenses/LICENSE-2.0
* HOMEPAGE:
* https://github.com/apple/swift-asn1
57 changes: 57 additions & 0 deletions Sources/JWTKit/RSA/RSAKey+KeyCalculation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import BigInt
import Foundation
import SwiftASN1

extension RSAKey {
/// Creates a new private key using modulus, exponent and private exponent.
static func calculatePrivateDER(n: Data, e: Data, d: Data) throws -> DERSerializable? {
let n = BigUInt(n)
let e = BigUInt(e)
let d = BigUInt(d)

// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Br1.pdf

let (p, q) = try PrimeGenerator.calculatePrimeFactors(n: n, e: e, d: d)

// https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Using_the_Chinese_remainder_algorithm

let dp = d % (p - 1)
let dq = d % (q - 1)

guard let qInv = q.inverse(p) else {
return nil
}

let key = RSAPrivateKeyASN1(
modulus: ArraySlice(n.byteArray),
publicExponent: ArraySlice(e.byteArray),
privateExponent: ArraySlice(d.byteArray),
prime1: ArraySlice(p.byteArray),
prime2: ArraySlice(q.byteArray),
exponent1: ArraySlice(dp.byteArray),
exponent2: ArraySlice(dq.byteArray),
coefficient: ArraySlice(qInv.byteArray)
)

return key
}

static func calculateDER(n: Data, e: Data) throws -> DERSerializable {
let n = BigUInt(n)
let e = BigUInt(e)

let key = RSAPublicKeyASN1(
modulus: ArraySlice(n.byteArray),
publicExponent: ArraySlice(e.byteArray)
)

return key
}
}

extension BigUInt {
var byteArray: [UInt8] {
// Remove any leading zero bytes (from the MSB side)
Array(serialize().drop(while: { $0 == 0 }))
}
}
119 changes: 0 additions & 119 deletions Sources/JWTKit/RSA/RSAKey+KeyGeneration.swift

This file was deleted.

124 changes: 124 additions & 0 deletions Sources/JWTKit/RSA/RSAKey+RSAPrivateKeyASN1.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import Foundation
import SwiftASN1

extension RSAKey {
/// From [RFC 8017 § A.1.2](https://www.rfc-editor.org/rfc/rfc8017#appendix-A.1.2):
///
/// ```
/// RSAPrivateKey ::= SEQUENCE {
/// version Version,
/// modulus INTEGER, -- n
/// publicExponent INTEGER, -- e
/// privateExponent INTEGER, -- d
/// prime1 INTEGER, -- p
/// prime2 INTEGER, -- q
/// exponent1 INTEGER, -- d mod (p-1)
/// exponent2 INTEGER, -- d mod (q-1)
/// coefficient INTEGER, -- (inverse of q) mod p
/// otherPrimeInfos OtherPrimeInfos OPTIONAL
/// }
/// ```
struct RSAPrivateKeyASN1: DERSerializable {
let version: UInt8
let modulus: ArraySlice<UInt8>
let publicExponent: ArraySlice<UInt8>
let privateExponent: ArraySlice<UInt8>
let prime1: ArraySlice<UInt8>
let prime2: ArraySlice<UInt8>
let exponent1: ArraySlice<UInt8>
let exponent2: ArraySlice<UInt8>
let coefficient: ArraySlice<UInt8>

init(
version: UInt8 = 0,
modulus: ArraySlice<UInt8>,
publicExponent: ArraySlice<UInt8>,
privateExponent: ArraySlice<UInt8>,
prime1: ArraySlice<UInt8>,
prime2: ArraySlice<UInt8>,
exponent1: ArraySlice<UInt8>,
exponent2: ArraySlice<UInt8>,
coefficient: ArraySlice<UInt8>
) {
self.version = version
self.modulus = modulus
self.publicExponent = publicExponent
self.privateExponent = privateExponent
self.prime1 = prime1
self.prime2 = prime2
self.exponent1 = exponent1
self.exponent2 = exponent2
self.coefficient = coefficient
}
}

/// Retrieves the RSA private key primitives.
///
/// This function extracts the modulus, public exponent, and private exponent from an RSA private key.
///
/// - Returns: A tuple containing the modulus, public exponent, and private exponent as Base64 URL-encoded strings.
/// - Throws: ``JWTError`` if the key is not a private RSA key or if there is an issue parsing the key.
public func getPrivateKeyPrimitives() throws -> (modulus: String, exponent: String, privateExponent: String) {
guard self.type == .private, let privateKey = self.privateKey else {
throw JWTError.generic(identifier: "rsaPrivateKey", reason: "Key is not a private RSA key")
}

let parsed = try DER.parse(Array(privateKey.derRepresentation))
let rsaPrivateKey = try RSAPrivateKeyASN1(derEncoded: parsed)

let modulus = String(decoding: Data(rsaPrivateKey.modulus).base64URLEncodedBytes(), as: UTF8.self)
let publicExponent = String(decoding: Data(rsaPrivateKey.publicExponent).base64URLEncodedBytes(), as: UTF8.self)
let privateExponent = String(decoding: Data(rsaPrivateKey.privateExponent).base64URLEncodedBytes(), as: UTF8.self)

return (modulus, publicExponent, privateExponent)
}
}

extension RSAKey.RSAPrivateKeyASN1: DERImplicitlyTaggable {
static var defaultIdentifier: ASN1Identifier {
.sequence
}

init(derEncoded: ASN1Node, withIdentifier identifier: ASN1Identifier) throws {
self = try DER.sequence(derEncoded, identifier: identifier) { nodes in
let version = try UInt8(derEncoded: &nodes)
guard version == 0 else {
throw ASN1Error.invalidASN1Object(reason: "Invalid version")
}

let modulus = try ArraySlice(derEncoded: &nodes)
let publicExponent = try ArraySlice<UInt8>(derEncoded: &nodes)
let privateExponent = try ArraySlice<UInt8>(derEncoded: &nodes)
let prime1 = try ArraySlice<UInt8>(derEncoded: &nodes)
let prime2 = try ArraySlice<UInt8>(derEncoded: &nodes)
let exponent1 = try ArraySlice<UInt8>(derEncoded: &nodes)
let exponent2 = try ArraySlice<UInt8>(derEncoded: &nodes)
let coefficient = try ArraySlice<UInt8>(derEncoded: &nodes)

return .init(
modulus: modulus,
publicExponent: publicExponent,
privateExponent: privateExponent,
prime1: prime1,
prime2: prime2,
exponent1: exponent1,
exponent2: exponent2,
coefficient: coefficient
)
}
}

func serialize(into coder: inout DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws {
try coder.appendConstructedNode(identifier: identifier) { coder in
try coder.serialize(self.version)
try coder.serialize(self.modulus)
try coder.serialize(self.publicExponent)
try coder.serialize(self.privateExponent)
try coder.serialize(self.prime1)
try coder.serialize(self.prime2)
try coder.serialize(self.exponent1)
try coder.serialize(self.exponent2)
try coder.serialize(self.coefficient)
}
}
}
62 changes: 62 additions & 0 deletions Sources/JWTKit/RSA/RSAKey+RSAPublicKeyASN1.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Foundation
import SwiftASN1

extension RSAKey {
/// From [RFC 8017 § A.1.2](https://www.rfc-editor.org/rfc/rfc8017#appendix-A.1.1):
///
/// ```
/// RSAPublicKey ::= SEQUENCE {
/// modulus INTEGER, -- n
/// publicExponent INTEGER -- e
/// }
/// ```
struct RSAPublicKeyASN1: DERSerializable {
let modulus: ArraySlice<UInt8>
let publicExponent: ArraySlice<UInt8>

init(modulus: ArraySlice<UInt8>, publicExponent: ArraySlice<UInt8>) {
self.modulus = modulus
self.publicExponent = publicExponent
}
}

/// Retrieves the RSA public key primitives.
///
/// This function extracts the modulus and public exponent from an RSA private key.
///
/// - Returns: A tuple containing the modulus and public exponent as Base64 URL-encoded strings.
/// - Throws: If there is an issue parsing the key.
public func getPublicKeyPrimitives() throws -> (modulus: String, exponent: String) {
let parsed = try DER.parse(Array(self.publicKey.derRepresentation))
let spki = try SubjectPublicKeyInfo(derEncoded: parsed)
let parsedKey = try DER.parse(spki.key.bytes)
let rsaPublicKey = try RSAPublicKeyASN1(derEncoded: parsedKey)

let modulus = String(decoding: Data(rsaPublicKey.modulus).base64URLEncodedBytes(), as: UTF8.self)
let exponent = String(decoding: Data(rsaPublicKey.publicExponent).base64URLEncodedBytes(), as: UTF8.self)

return (modulus, exponent)
}
}

extension RSAKey.RSAPublicKeyASN1: DERImplicitlyTaggable {
static var defaultIdentifier: SwiftASN1.ASN1Identifier {
.sequence
}

init(derEncoded rootNode: SwiftASN1.ASN1Node, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws {
self = try DER.sequence(rootNode, identifier: identifier) { nodes in
let modulus = try ArraySlice(derEncoded: &nodes)
let publicExponent = try ArraySlice<UInt8>(derEncoded: &nodes)

return .init(modulus: modulus, publicExponent: publicExponent)
}
}

func serialize(into coder: inout SwiftASN1.DER.Serializer, withIdentifier identifier: SwiftASN1.ASN1Identifier) throws {
try coder.appendConstructedNode(identifier: identifier) { coder in
try coder.serialize(self.modulus)
try coder.serialize(self.publicExponent)
}
}
}
Loading

0 comments on commit 7ceeaa7

Please sign in to comment.