Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add PSS signing and verifying #46

Merged
merged 4 commits into from
May 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}