Skip to content

Commit

Permalink
Merge pull request #46 from IBM-Swift/pssRSA
Browse files Browse the repository at this point in the history
feat: Add PSS signing and verifying
  • Loading branch information
billabt authored May 14, 2019
2 parents c4e5055 + 95a0bf0 commit 41cce36
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 10 deletions.
63 changes: 53 additions & 10 deletions Sources/CryptorRSA/CryptorRSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -765,10 +765,11 @@ public class CryptorRSA {
/// - Parameters:
/// - key: The `PrivateKey`.
/// - algorithm: The algorithm to use (`Data.Algorithm`).
/// - usePSS: Bool stating whether or not to use RSA-PSS (Probabilistic signature scheme).
///
/// - Returns: A new optional `SignedData` containing the digital signature.
///
public func signed(with key: PrivateKey, algorithm: Data.Algorithm) throws -> SignedData? {
public func signed(with key: PrivateKey, algorithm: Data.Algorithm, usePSS: Bool = false) throws -> SignedData? {

// Must be plaintext...
guard self.type == .plaintextType else {
Expand All @@ -793,9 +794,20 @@ public class CryptorRSA {

let (md, _) = algorithm.algorithmForSignature

// Provide a pkey_ctx to EVP_DigestSignInit so that the EVP_PKEY_CTX of the signing operation
// is written to it, to allow alternative signing options to be set
EVP_DigestSignInit(md_ctx, nil, .make(optional: md), nil, .make(optional: key.reference))
if usePSS {
// If using PSS padding, create a PKEY and set it's padding, mask generation function and salt length.
// NID_rsassaPss is `EVP_PKEY_RSA_PSS` as defined in evp.h
var pkey_ctx = EVP_PKEY_CTX_new_id(NID_rsassaPss, nil)
EVP_DigestSignInit(md_ctx, &pkey_ctx, .make(optional: md), nil, .make(optional: key.reference))
EVP_PKEY_CTX_ctrl(pkey_ctx, EVP_PKEY_RSA, -1, EVP_PKEY_CTRL_RSA_PADDING, RSA_PKCS1_PSS_PADDING, nil)
EVP_PKEY_CTX_ctrl(pkey_ctx, -1, EVP_PKEY_OP_SIGN, EVP_PKEY_CTRL_RSA_MGF1_MD, 0, .make(optional: md))
// Sets salt length to be equal to message digest length
EVP_PKEY_CTX_ctrl(pkey_ctx, -1, EVP_PKEY_OP_SIGN, EVP_PKEY_CTRL_RSA_PSS_SALTLEN, -1, .make(optional: md))
} else {
// Provide a pkey_ctx to EVP_DigestSignInit so that the EVP_PKEY_CTX of the signing operation
// is written to it, to allow alternative signing options to be set
EVP_DigestSignInit(md_ctx, nil, .make(optional: md), nil, .make(optional: key.reference))
}

// Convert Data to UnsafeRawPointer!
_ = self.data.withUnsafeBytes({ (message: UnsafeRawBufferPointer) -> Int32 in
Expand Down Expand Up @@ -829,8 +841,19 @@ public class CryptorRSA {

#else

let signingAlgorithm: SecKeyAlgorithm
if usePSS {
if #available(macOS 10.13, iOS 10.0, *) {
signingAlgorithm = usePSS ? algorithm.algorithmForPssSignature : algorithm.algorithmForSignature
} else {
throw Error(code: ERR_NOT_IMPLEMENTED, reason: "RSA-PSS only supported on macOS 10.13/iOS 10.0 and above.")
}
} else {
signingAlgorithm = algorithm.algorithmForSignature
}

var response: Unmanaged<CFError>? = nil
let sData = SecKeyCreateSignature(key.reference, algorithm.algorithmForSignature, self.data as CFData, &response)
let sData = SecKeyCreateSignature(key.reference, signingAlgorithm, self.data as CFData, &response)
if response != nil {

guard let error = response?.takeRetainedValue() else {
Expand All @@ -853,10 +876,11 @@ public class CryptorRSA {
/// - key: The `PublicKey`.
/// - signature: The `SignedData` containing the signature to verify against.
/// - algorithm: The algorithm to use (`Data.Algorithm`).
/// - usePSS: Bool stating whether or not to use RSA-PSS (Probabilistic signature scheme).
///
/// - Returns: True if verification is successful, false otherwise
///
public func verify(with key: PublicKey, signature: SignedData, algorithm: Data.Algorithm) throws -> Bool {
public func verify(with key: PublicKey, signature: SignedData, algorithm: Data.Algorithm, usePSS: Bool = false) throws -> Bool {

// Must be plaintext...
guard self.type == .plaintextType else {
Expand Down Expand Up @@ -886,9 +910,17 @@ public class CryptorRSA {

let (md, _) = algorithm.algorithmForSignature

// Provide a pkey_ctx to EVP_DigestSignInit so that the EVP_PKEY_CTX of the signing operation
// is written to it, to allow alternative signing options to be set
EVP_DigestVerifyInit(md_ctx, nil, .make(optional: md), nil, .make(optional: key.reference))
if usePSS {
// If using PSS padding, create a PKEY and set it's padding and mask generation function.
var pkey_ctx = EVP_PKEY_CTX_new_id(NID_rsassaPss, nil)
EVP_DigestVerifyInit(md_ctx, &pkey_ctx, .make(optional: md), nil, .make(optional: key.reference))
EVP_PKEY_CTX_ctrl(pkey_ctx, EVP_PKEY_RSA, -1, EVP_PKEY_CTRL_RSA_PADDING, RSA_PKCS1_PSS_PADDING, nil)
EVP_PKEY_CTX_ctrl(pkey_ctx, -1, EVP_PKEY_OP_VERIFY, EVP_PKEY_CTRL_RSA_MGF1_MD, 0, .make(optional: md))
} else {
// Provide a pkey_ctx to EVP_DigestSignInit so that the EVP_PKEY_CTX of the signing operation
// is written to it, to allow alternative signing options to be set
EVP_DigestVerifyInit(md_ctx, nil, .make(optional: md), nil, .make(optional: key.reference))
}


var rc = self.data.withUnsafeBytes({ (message: UnsafeRawBufferPointer) -> Int32 in
Expand All @@ -915,8 +947,19 @@ public class CryptorRSA {

#else

let signingAlgorithm: SecKeyAlgorithm
if usePSS {
if #available(macOS 10.13, iOS 10.0, *) {
signingAlgorithm = usePSS ? algorithm.algorithmForPssSignature : algorithm.algorithmForSignature
} else {
throw Error(code: ERR_NOT_IMPLEMENTED, reason: "RSA-PSS only supported on macOS 10.13/iOS 10.0 and above.")
}
} else {
signingAlgorithm = algorithm.algorithmForSignature
}

var response: Unmanaged<CFError>? = nil
let result = SecKeyVerifySignature(key.reference, algorithm.algorithmForSignature, self.data as CFData, signature.data as CFData, &response)
let result = SecKeyVerifySignature(key.reference, signingAlgorithm, self.data as CFData, signature.data as CFData, &response)
if response != nil {

guard let error = response?.takeRetainedValue() else {
Expand Down
25 changes: 25 additions & 0 deletions Sources/CryptorRSA/CryptorRSADigest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,31 @@ extension Data {
return .rsaSignatureMessagePKCS1v15SHA1
}
}

@available(macOS 10.13, iOS 10.0, *)
var algorithmForPssSignature: SecKeyAlgorithm {
switch self {

case .sha1:
return .rsaSignatureMessagePSSSHA1

case .sha224:
return .rsaSignatureMessagePSSSHA224

case .sha256:
return .rsaSignatureMessagePSSSHA256

case .sha384:
return .rsaSignatureMessagePSSSHA384

case .sha512:
return .rsaSignatureMessagePSSSHA512

case .gcm:
return .rsaSignatureMessagePSSSHA1

}
}

@available(macOS 10.12, iOS 10.0, *)
public var alogrithmForEncryption: SecKeyAlgorithm {
Expand Down
5 changes: 5 additions & 0 deletions Sources/CryptorRSA/CryptorRSAKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,11 @@ extension CryptorRSA {
let asn1 = UnsafeMutablePointer<UInt8>.allocate(capacity: 3500)
let readLength = BIO_read(asn1Bio, asn1, 3500)
let pemData = Data(bytes: asn1, count: Int(readLength))
#if swift(>=4.1)
asn1.deallocate()
#else
asn1.deallocate(capacity: 3500)
#endif
guard let pemString = String(data: pemData, encoding: .utf8) else {
throw Error(code: ERR_INIT_PK, reason: "Couldn't utf8 decode pemString")
}
Expand Down
118 changes: 118 additions & 0 deletions Tests/CryptorRSATests/CryptorRSATests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,102 @@ cSNAr2BBC8bJ9AfZnRu9+Y1/VyXY91R95bQoMFfgwZdMUEyuL5gG524QplqF
print("Test of algorithm: \(name) succeeded")
}
}

func test_signVerifyAllDigestTypesPSS() throws {

// PSS is only supported from swift 4.1 onwards
#if !swift(>=4.1)
return
#endif

let algorithms: [(Data.Algorithm, String)] = [(.sha1, ".sha1"),
(.sha224, ".sha224"),
(.sha256, ".sha256"),
(.sha384, ".sha384"),
(.gcm, ".gcm"),
/*(.sha512, ".sha512")*/]
// Test all the algorithms available...
// Note: .sha512 pss appears to be broken internally on Apple platforms, so we skip it...
guard let publicKey = self.publicKey,
let privateKey = self.privateKey else {
XCTFail("Could not find key")
return
}

// Test all the algorithms available...
for (algorithm, name) in algorithms {

print("Testing algorithm: \(name)")
let data = CryptorRSATests.randomData(count: 8192)
let message = CryptorRSA.createPlaintext(with: data)
let signature = try message.signed(with: privateKey, algorithm: algorithm, usePSS: true)
XCTAssertNotNil(signature)
let verificationResult = try message.verify(with: publicKey, signature: signature!, algorithm: algorithm, usePSS: true)
XCTAssertTrue(verificationResult)
print("Test of algorithm: \(name) succeeded")
}
}

func test_signVerifyBase64PSS() throws {

// PSS is only supported from swift 4.1 onwards
#if !swift(>=4.1)
return
#endif

let algorithms: [(Data.Algorithm, String)] = [(.sha1, ".sha1"),
(.sha224, ".sha224"),
(.sha256, ".sha256"),
(.sha384, ".sha384"),
(.gcm, ".gcm"),
/*(.sha512, ".sha512")*/]
// Test all the algorithms available...
// Note: .sha512 pss appears to be broken internally on Apple platforms, so we skip it...

guard let publicKey = self.publicKey,
let privateKey = self.privateKey else {
XCTFail("Could not find key")
return
}

// Test all the algorithms available...
for (algorithm, name) in algorithms {

print("Testing algorithm: \(name)")
let data = CryptorRSATests.randomData(count: 8192)
let message = CryptorRSA.createPlaintext(with: data)
let signature = try message.signed(with: privateKey, algorithm: algorithm, usePSS: true)
XCTAssertNotNil(signature)
XCTAssertEqual(signature!.base64String, signature!.data.base64EncodedString())
let verificationResult = try message.verify(with: publicKey, signature: signature!, algorithm: algorithm, usePSS: true)
XCTAssertTrue(verificationResult)
print("Test of algorithm: \(name) succeeded")
}
}

func test_verifyExtenalPSSSignature() {

// PSS is only supported from swift 4.1 onwards
#if !swift(>=4.1)
return
#endif

guard let publicKey = self.publicKey else {
XCTFail("Could not find key")
return
}
// Generated by jwt.io
let externalMessage = "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0"
let externalSignature = "Itey9AhjNgb1owBaVsTE-7NrZY1c7AJtp990w4AJRZWMOeX-2UiVdil9vflW7BkduRXMA83hCdhQjqzvnJGhxEVllZshPYvueW0otxzI-wl4fPY6ai6qiBh9JzDwFlb9IHyIDGhr3HHKaMjYEwpt8VJYxzEcHwdGg34aczspM0U"
let message = CryptorRSA.createPlaintext(with: Data(externalMessage.utf8))
let signature = CryptorRSA.createSigned(with: Data(base64urlEncoded: externalSignature)!)
do {
let verificationResult = try message.verify(with: publicKey, signature: signature, algorithm: .sha256, usePSS: true)
XCTAssertTrue(verificationResult)
} catch {
XCTFail("Error thrown during verification: \(error)")
}
}

func test_verifyAppIDToken() throws {

Expand Down Expand Up @@ -772,9 +868,31 @@ cSNAr2BBC8bJ9AfZnRu9+Y1/VyXY91R95bQoMFfgwZdMUEyuL5gG524QplqF
("test_randomByteEncryption", test_randomByteEncryption),
("test_signVerifyAllDigestTypes", test_signVerifyAllDigestTypes),
("test_signVerifyBase64", test_signVerifyBase64),
("test_signVerifyAllDigestTypesPSS", test_signVerifyAllDigestTypesPSS),
("test_signVerifyBase64PSS", test_signVerifyBase64PSS),
("test_verifyExtenalPSSSignature", test_verifyExtenalPSSSignature),
("test_verifyAppIDToken", test_verifyAppIDToken),
("test_makeKeyPair", test_makeKeyPair),
]
}
}

private extension Data {

func base64urlEncodedString() -> String {
let result = self.base64EncodedString()
return result.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}

init?(base64urlEncoded: String) {
let paddingLength = 4 - base64urlEncoded.count % 4
let padding = (paddingLength < 4) ? String(repeating: "=", count: paddingLength) : ""
let base64EncodedString = base64urlEncoded
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
+ padding
self.init(base64Encoded: base64EncodedString)
}
}

0 comments on commit 41cce36

Please sign in to comment.