-
-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add option to fetch RSA primitives (#127)
* Add option to fetch RSA primitives * Typo * Remove unused files * Update NOTICES
- Loading branch information
Showing
8 changed files
with
409 additions
and
122 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 })) | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
Oops, something went wrong.