Files
swiftGrammar/Pods/BlueRSA/Sources/CryptorRSA/CryptorRSA.swift
2024-08-12 10:49:20 +08:00

1112 lines
39 KiB
Swift

//
// CryptorRSA.swift
// CryptorRSA
//
// Created by Bill Abt on 1/17/17.
//
// Copyright © 2017 IBM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
#if os(Linux)
import OpenSSL
#endif
// MARK: -
// MARK: -
///
/// RSA Encryption/Decryption, Signing/Verification
///
@available(macOS 10.12, iOS 10.3, watchOS 3.3, tvOS 12.0, *)
public class CryptorRSA {
// MARK: Class Functions
///
/// Create a plaintext data container.
///
/// - Parameters:
/// - data: `Data` containing the key data.
///
/// - Returns: Newly initialized `PlaintextData`.
///
public class func createPlaintext(with data: Data) -> PlaintextData {
return PlaintextData(with: data)
}
///
/// Creates a message from a plaintext string, with the specified encoding.
///
/// - Parameters:
/// - string: String value of the plaintext message
/// - encoding: Encoding to use to generate the clear data
///
/// - Returns: Newly initialized `PlaintextData`.
///
public class func createPlaintext(with string: String, using encoding: String.Encoding) throws -> PlaintextData {
return try PlaintextData(with: string, using: encoding)
}
///
/// Create an encrypted data container.
///
/// - Parameters:
/// - data: `Data` containing the encrypted data.
///
/// - Returns: Newly initialized `EncryptedData`.
///
public class func createEncrypted(with data: Data) -> EncryptedData {
return EncryptedData(with: data)
}
///
/// Creates a message with a encrypted base64-encoded string.
///
/// - Parameters:
/// - base64String: Base64-encoded data of an encrypted message
///
/// - Returns: Newly initialized `EncryptedData`.
///
public class func createEncrypted(with base64String: String) throws -> EncryptedData {
return try EncryptedData(withBase64: base64String)
}
///
/// Create an signed data container.
///
/// - Parameters:
/// - data: `Data` containing the signed data.
///
/// - Returns: Newly initialized `SignedData`.
///
public class func createSigned(with data: Data) -> SignedData {
return SignedData(with: data)
}
///
/// RSA Data Object: Allows for RSA Encryption/Decryption, Signing/Verification and various utility functions.
///
public class RSAData {
// MARK: Enums
/// Denotes the type of data this represents.
public enum DataType {
/// Plaintext
case plaintextType
/// Encrypted
case encryptedType
/// Signed
case signedType
}
// MARK: -- Properties
/// Data of the message
public let data: Data
/// Represents the type of data contained.
public internal(set) var type: DataType = .plaintextType
/// Base64-encoded string of the message data
public var base64String: String {
return data.base64EncodedString()
}
// MARK: -- Initializers
///
/// Initialize a new RSAData object.
///
/// - Parameters:
/// - data: `Data` containing the data.
/// - type: Type of data contained.
///
/// - Returns: Newly initialized `RSAData`.
///
internal init(with data: Data, type: DataType) {
self.data = data
self.type = type
}
///
/// Creates a RSAData with a encrypted base64-encoded string.
///
/// - Parameters:
/// - base64String: Base64-encoded data of an encrypted message
///
/// - Returns: Newly initialized `RSAData`.
///
internal init(withBase64 base64String: String) throws {
guard let data = Data(base64Encoded: base64String) else {
throw Error(code: CryptorRSA.ERR_BASE64_PEM_DATA, reason: "Couldn't convert base 64 encoded string ")
}
self.data = data
self.type = .encryptedType
}
///
/// Creates a message from a plaintext string, with the specified encoding.
///
/// - Parameters:
/// - string: String value of the plaintext message
/// - encoding: Encoding to use to generate the clear data
///
/// - Returns: Newly initialized `RSAData`.
///
internal init(with string: String, using encoding: String.Encoding) throws {
guard let data = string.data(using: encoding) else {
throw Error(code: CryptorRSA.ERR_STRING_ENCODING, reason: "Couldn't convert string to data using specified encoding")
}
self.data = data
self.type = .plaintextType
}
// MARK: -- Functions
// MARK: --- Encrypt/Decrypt
///
/// Encrypt the data.
///
/// - Parameters:
/// - key: The `PublicKey`
/// - algorithm: The algorithm to use (`Data.Algorithm`).
///
/// - Returns: A new optional `EncryptedData` containing the encrypted data.
///
public func encrypted(with key: PublicKey, algorithm: Data.Algorithm) throws -> EncryptedData? {
// Must be plaintext...
guard self.type == .plaintextType else {
throw Error(code: CryptorRSA.ERR_NOT_PLAINTEXT, reason: "Data is not plaintext")
}
// Key must be public...
guard key.type == .publicType else {
throw Error(code: CryptorRSA.ERR_KEY_NOT_PUBLIC, reason: "Supplied key is not public")
}
#if os(Linux)
switch algorithm {
case .gcm:
return try encryptedGCM(with: key)
case .sha1, .sha224, .sha256, .sha384, .sha512:
// Same algorithm is used regardless of sha
return try encryptedCBC(with: key)
}
#else
var response: Unmanaged<CFError>? = nil
let eData = SecKeyCreateEncryptedData(key.reference, algorithm.alogrithmForEncryption, self.data as CFData, &response)
if response != nil {
guard let error = response?.takeRetainedValue() else {
throw Error(code: CryptorRSA.ERR_ENCRYPTION_FAILED, reason: "Encryption failed. Unable to determine error.")
}
throw Error(code: CryptorRSA.ERR_ENCRYPTION_FAILED, reason: "Encryption failed with error: \(error)")
}
return EncryptedData(with: eData! as Data)
#endif
}
///
/// Decrypt the data.
///
/// - Parameters:
/// - key: The `PrivateKey`
/// - algorithm: The algorithm to use (`Data.Algorithm`).
///
/// - Returns: A new optional `PlaintextData` containing the decrypted data.
///
public func decrypted(with key: PrivateKey, algorithm: Data.Algorithm) throws -> PlaintextData? {
// Must be encrypted...
guard self.type == .encryptedType else {
throw Error(code: CryptorRSA.ERR_NOT_ENCRYPTED, reason: "Data is plaintext")
}
// Key must be private...
guard key.type == .privateType else {
throw Error(code: CryptorRSA.ERR_KEY_NOT_PUBLIC, reason: "Supplied key is not private")
}
#if os(Linux)
switch algorithm {
case .gcm:
return try decryptedGCM(with: key)
case .sha1, .sha224, .sha256, .sha384, .sha512:
// Same algorithm is used regardless of sha
return try decryptedCBC(with: key)
}
#else
var response: Unmanaged<CFError>? = nil
let pData = SecKeyCreateDecryptedData(key.reference, algorithm.alogrithmForEncryption, self.data as CFData, &response)
if response != nil {
guard let error = response?.takeRetainedValue() else {
throw Error(code: CryptorRSA.ERR_DECRYPTION_FAILED, reason: "Decryption failed. Unable to determine error.")
}
throw Error(code: CryptorRSA.ERR_DECRYPTION_FAILED, reason: "Decryption failed with error: \(error)")
}
return PlaintextData(with: pData! as Data)
#endif
}
#if os(Linux)
///
/// Encrypt the data using AES GCM SHA1 for cross platform support.
///
/// - Parameters:
/// - key: Public key to use.
///
/// - Returns: Encrypted data object.
///
func encryptedGCM(with key: PublicKey) throws -> EncryptedData? {
// Initialize encryption context
let rsaEncryptCtx = EVP_CIPHER_CTX_new_wrapper()
EVP_CIPHER_CTX_init_wrapper(rsaEncryptCtx)
defer {
// On completion deallocate the memory
EVP_CIPHER_CTX_reset_wrapper(rsaEncryptCtx)
EVP_CIPHER_CTX_free_wrapper(rsaEncryptCtx)
}
// get rsaKey
guard let rsaKey = EVP_PKEY_get1_RSA(.make(optional: key.reference)) else {
let source = "Couldn't create key reference from key data"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_ADD_KEY, reason: reason)
}
throw Error(code: ERR_ADD_KEY, reason: source + ": No OpenSSL error reported.")
}
defer {
RSA_free(rsaKey)
}
// Set the additional authenticated data (aad) as the RSA key modulus and publicExponent in an ASN1 sequence.
guard let aad = key.publicKeyBytes else {
let source = "Encryption failed"
throw Error(code: ERR_ENCRYPTION_FAILED, reason: source + ": Failed to decode public key")
}
// if the RSA key is >= 4096 bits, use aes_256_gcm.
let encryptedCapacity: Int
let keySize: Int
if aad.count > 525 {
// Set the rsaEncryptCtx to use EVP_aes_256_gcm encryption.
guard EVP_EncryptInit_ex(rsaEncryptCtx, EVP_aes_256_gcm(), nil, nil, nil) == 1 else {
throw Error(code: ERR_ENCRYPTION_FAILED, reason: "Encryption failed: Failed to initialize encryption context")
}
encryptedCapacity = 512
keySize = 32
} else {
// Set the rsaEncryptCtx to use EVP_aes_128_gcm encryption.
guard EVP_EncryptInit_ex(rsaEncryptCtx, EVP_aes_128_gcm(), nil, nil, nil) == 1 else {
throw Error(code: ERR_ENCRYPTION_FAILED, reason: "Encryption failed: Failed to initialize encryption context")
}
if aad.count > 300 {
encryptedCapacity = 384
} else if aad.count > 260 {
encryptedCapacity = 256
} else {
encryptedCapacity = 128
}
keySize = 16
}
// Allocate encryption memory
let aeskey = UnsafeMutablePointer<UInt8>.allocate(capacity: keySize)
let encryptedKey = UnsafeMutablePointer<UInt8>.allocate(capacity: encryptedCapacity)
let tag = UnsafeMutablePointer<UInt8>.allocate(capacity: 16)
let encrypted = UnsafeMutablePointer<UInt8>.allocate(capacity: data.count + 16)
defer {
#if swift(>=4.1)
aeskey.deallocate()
encryptedKey.deallocate()
tag.deallocate()
encrypted.deallocate()
#else
aeskey.deallocate(capacity: keySize)
encryptedKey.deallocate(capacity: encryptedCapacity)
tag.deallocate(capacity: 16)
encrypted.deallocate(capacity: data.count + 16)
#endif
}
var processedLength: Int32 = 0
var encLength: Int32 = 0
// Apple use a 16 byte all 0 IV. This is allowed since a random key is generated for each encryption.
let iv = [UInt8](repeating: 0, count: 16)
// Set the IV length to be 16 to match Apple.
guard EVP_CIPHER_CTX_ctrl(rsaEncryptCtx, EVP_CTRL_GCM_SET_IVLEN, 16, nil) == 1,
// Generate 16/32 random bytes that will be used as the AES key.
EVP_CIPHER_CTX_rand_key(rsaEncryptCtx, aeskey) == 1,
// Set the aeskey and iv for the symmetric encryption.
EVP_EncryptInit_ex(rsaEncryptCtx, nil, nil, aeskey, iv) == 1,
// Encrypt the aes key using the rsa public key with SHA1, OAEP padding.
RSA_public_encrypt(Int32(keySize), aeskey, encryptedKey, .make(optional: rsaKey), RSA_PKCS1_OAEP_PADDING) == encryptedCapacity,
// Add the aad to the encryption context.
// This is used in generating the GCM tag. We don't use this processedLength.
EVP_EncryptUpdate(rsaEncryptCtx, nil, &processedLength, [UInt8](aad), Int32(aad.count)) == 1
else {
let source = "Encryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_ENCRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_ENCRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
// Encrypt the plaintext into encrypted using gcmAlgorithm with the random aes key and all 0 iv.
guard(self.data.withUnsafeBytes({ (plaintext: UnsafeRawBufferPointer) -> Int32 in
return EVP_EncryptUpdate(rsaEncryptCtx, encrypted, &processedLength, plaintext.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(data.count))
})) == 1 else {
let source = "Encryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_ENCRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_ENCRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
encLength += processedLength
// Finalize the encryption so no more data will be added and generate the GCM tag.
guard EVP_EncryptFinal_ex(rsaEncryptCtx, encrypted.advanced(by: Int(encLength)), &processedLength) == 1 else {
let source = "Encryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_ENCRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_ENCRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
encLength += processedLength
// Get the 16 byte GCM tag.
guard EVP_CIPHER_CTX_ctrl(rsaEncryptCtx, EVP_CTRL_GCM_GET_TAG, 16, tag) == 1 else {
let source = "Encryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_ENCRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_ENCRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
// Construct the envelope by combining the encrypted AES key, the encrypted date and the GCM tag.
let ekFinal = Data(bytes: encryptedKey, count: encryptedCapacity)
let cipher = Data(bytes: encrypted, count: Int(encLength))
let tagFinal = Data(bytes: tag, count: 16)
return EncryptedData(with: ekFinal + cipher + tagFinal)
}
///
/// Encrypt the data using CBC for cross platform support.
///
/// - Parameters:
/// - key: Public key to use.
///
/// - Returns: Encrypted data object.
///
func encryptedCBC(with key: PublicKey) throws -> EncryptedData? {
// Copy the EVP Key
var evp_key = EVP_PKEY_new()
let rsa = EVP_PKEY_get1_RSA(.make(optional: key.reference))
EVP_PKEY_set1_RSA(evp_key, rsa)
RSA_free(rsa)
defer {
EVP_PKEY_free(evp_key)
}
// TODO: hash type option is not being used right now.
let enc = EVP_aes_256_cbc()
let padding = RSA_PKCS1_OAEP_PADDING
let rsaEncryptCtx = EVP_CIPHER_CTX_new_wrapper()
defer {
EVP_CIPHER_CTX_reset_wrapper(rsaEncryptCtx)
EVP_CIPHER_CTX_free_wrapper(rsaEncryptCtx)
}
EVP_CIPHER_CTX_set_padding(rsaEncryptCtx, padding)
// Initialize the AES encryption key array (of size 1)
typealias UInt8Ptr = UnsafeMutablePointer<UInt8>?
var ek: UInt8Ptr
ek = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(EVP_PKEY_size(.make(optional: key.reference))))
let ekPtr = UnsafeMutablePointer<UInt8Ptr>.allocate(capacity: MemoryLayout<UInt8Ptr>.size)
ekPtr.pointee = ek
// Assign size of the corresponding cipher's IV
let IVLength = EVP_CIPHER_iv_length(.make(optional: enc))
let iv = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(IVLength))
let encrypted = UnsafeMutablePointer<UInt8>.allocate(capacity: self.data.count + Int(IVLength))
defer {
#if swift(>=4.1)
ek?.deallocate()
ekPtr.deallocate()
iv.deallocate()
encrypted.deallocate()
#else
ek?.deallocate(capacity: Int(EVP_PKEY_size(.make(optional: key.reference))))
ekPtr.deallocate(capacity: MemoryLayout<UInt8Ptr>.size)
iv.deallocate(capacity: Int(IVLength))
encrypted.deallocate(capacity: self.data.count + Int(IVLength))
#endif
}
var encKeyLength: Int32 = 0
var processedLength: Int32 = 0
var encLength: Int32 = 0
// Initializes a cipher context ctx for encryption with cipher type using a random secret key and IV.
// The secret key is encrypted using the public key (evp_key can be an array of public keys)
// Here we are using just 1 public key
var status = EVP_SealInit(rsaEncryptCtx, .make(optional: enc), ekPtr, &encKeyLength, iv, &evp_key, 1)
// SealInit should return the number of public keys that were input, here it is only 1
guard status == 1 else {
let source = "Encryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_ENCRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_ENCRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
// EVP_SealUpdate is a complex macros and therefore the compiler doesnt
// convert it directly to swift. From /usr/local/opt/openssl/include/openssl/evp.h:
_ = self.data.withUnsafeBytes({ (plaintext: UnsafeRawBufferPointer) -> Int32 in
return EVP_EncryptUpdate(rsaEncryptCtx, encrypted, &processedLength, plaintext.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(self.data.count))
})
encLength = processedLength
status = EVP_SealFinal(rsaEncryptCtx, encrypted.advanced(by: Int(encLength)), &processedLength)
guard status == 1 else {
let source = "Encryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_ENCRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_ENCRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
encLength += processedLength
let cipher = Data(bytes: encrypted, count: Int(encLength))
let ekFinal = Data(bytes: ek!, count: Int(encKeyLength))
let ivFinal = Data(bytes: iv, count: Int(IVLength))
return EncryptedData(with: ekFinal + cipher + ivFinal)
}
///
/// Decrypt the data using AES GCM for cross platform support.
///
/// - Parameters:
/// - key: Private key to use.
///
/// - Returns: Decrypted data object.
///
func decryptedGCM(with key: PrivateKey) throws -> PlaintextData? {
// Initialize the decryption context.
let rsaDecryptCtx = EVP_CIPHER_CTX_new()
EVP_CIPHER_CTX_init_wrapper(rsaDecryptCtx)
defer {
// On completion deallocate the memory
EVP_CIPHER_CTX_reset_wrapper(rsaDecryptCtx)
EVP_CIPHER_CTX_free_wrapper(rsaDecryptCtx)
}
// get rsaKey
guard let rsaKey = EVP_PKEY_get1_RSA(.make(optional: key.reference)) else {
let source = "Couldn't create key reference from key data"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_ADD_KEY, reason: reason)
}
throw Error(code: ERR_ADD_KEY, reason: source + ": No OpenSSL error reported.")
}
defer {
RSA_free(rsaKey)
}
// Set the additional authenticated data (aad) as the RSA key modulus and publicExponent in an ASN1 sequence.
guard let aad = key.publicKeyBytes else {
let source = "Decryption failed"
throw Error(code: ERR_DECRYPTION_FAILED, reason: source + ": Failed to decode public key")
}
// if the RSA key is larger than 4096 bits, use aes_256_gcm.
let encKeyLength: Int
let keySize: Int
if aad.count > 525 {
// Set the envelope decryption algorithm as 128 bit AES-GCM.
guard EVP_DecryptInit_ex(rsaDecryptCtx, EVP_aes_256_gcm(), nil, nil, nil) == 1 else {
throw Error(code: ERR_DECRYPTION_FAILED, reason: "Decryption failed: Failed to initialize decryption context")
}
encKeyLength = 512
keySize = 32
} else {
// Set the envelope decryption algorithm as 128 bit AES-GCM.
guard EVP_DecryptInit_ex(rsaDecryptCtx, EVP_aes_128_gcm(), nil, nil, nil) == 1 else {
throw Error(code: ERR_DECRYPTION_FAILED, reason: "Decryption failed: Failed to initialize decryption context")
}
if aad.count > 300 {
encKeyLength = 384
} else if aad.count > 260 {
encKeyLength = 256
} else {
encKeyLength = 128
}
keySize = 16
}
let tagLength = 16
let encryptedDataLength = Int(data.count) - encKeyLength - tagLength
// Extract encryptedAESKey, encryptedData, GCM tag from data
let encryptedKey = data.subdata(in: 0..<encKeyLength)
let encryptedData = data.subdata(in: encKeyLength..<encKeyLength+encryptedDataLength)
var tagData = data.subdata(in: encKeyLength+encryptedDataLength..<data.count)
// Allocate memory for decryption
let aeskey = UnsafeMutablePointer<UInt8>.allocate(capacity: keySize)
let decrypted = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(encryptedData.count + 16))
defer {
// On completion deallocate the memory
#if swift(>=4.1)
aeskey.deallocate()
decrypted.deallocate()
#else
aeskey.deallocate(capacity: keySize)
decrypted.deallocate(capacity: Int(encryptedData.count + 16))
#endif
}
// processedLen is the number of bytes that each EVP_DecryptUpdate/EVP_DecryptFinal decrypts.
// The sum of processedLen is the total size of the decrypted message (decMsgLen)
var processedLen: Int32 = 0
var decMsgLen: Int32 = 0
// Use a 16-byte all zero initialization vector (IV) to match Apple Security.
let iv = [UInt8](repeating: 0, count: 16)
// Decrypt the encryptedKey into the aeskey using the RSA private key
guard RSA_private_decrypt(Int32(encryptedKey.count), [UInt8](encryptedKey), aeskey, rsaKey, RSA_PKCS1_OAEP_PADDING) != 0,
// Set the IV length to be 16 bytes.
EVP_CIPHER_CTX_ctrl(rsaDecryptCtx, EVP_CTRL_GCM_SET_IVLEN, 16, nil) == 1,
// Set the AES key to be 16 bytes.
EVP_CIPHER_CTX_set_key_length(rsaDecryptCtx, Int32(keySize)) == 1,
// Set the envelope decryption context AES key and IV.
EVP_DecryptInit_ex(rsaDecryptCtx, nil, nil, aeskey, iv) == 1,
EVP_DecryptUpdate(rsaDecryptCtx, nil, &processedLen, [UInt8](aad), Int32(aad.count)) == 1
else {
let source = "Decryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_DECRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_DECRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
// Decrypt the encrypted data using the symmetric key.
guard encryptedData.withUnsafeBytes({ (enc: UnsafeRawBufferPointer) -> Int32 in
return EVP_DecryptUpdate(rsaDecryptCtx, decrypted, &processedLen, enc.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(encryptedData.count))
}) != 0 else {
let source = "Decryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_DECRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_DECRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
decMsgLen += processedLen
// Verify the provided GCM tag.
guard tagData.withUnsafeMutableBytes({ (tag: UnsafeMutableRawBufferPointer) -> Int32 in
return EVP_CIPHER_CTX_ctrl(rsaDecryptCtx, EVP_CTRL_GCM_SET_TAG, 16, tag.baseAddress)
}) == 1,
EVP_DecryptFinal_ex(rsaDecryptCtx, decrypted.advanced(by: Int(decMsgLen)), &processedLen) == 1
else {
let source = "Decryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_DECRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_DECRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
decMsgLen += processedLen
// return the decrypted plaintext.
return PlaintextData(with: Data(bytes: decrypted, count: Int(decMsgLen)))
}
///
/// Decrypt the data using CBC for cross platform support.
///
/// - Parameters:
/// - key: Private key to use.
///
/// - Returns: Decrypted data object.
///
func decryptedCBC(with key: PrivateKey) throws -> PlaintextData? {
// Convert RSA key to EVP
let encType = EVP_aes_256_cbc()
let padding = RSA_PKCS1_OAEP_PADDING
// Size of symmetric encryption
let encKeyLength = Int(EVP_PKEY_size(.make(optional: key.reference)))
// Size of the corresponding cipher's IV
let encIVLength = Int(EVP_CIPHER_iv_length(.make(optional: encType)))
// Size of encryptedKey
let encryptedDataLength = Int(self.data.count) - encKeyLength - encIVLength
// Extract encryptedKey, encryptedData, encryptedIV from data
// self.data = encryptedKey + encryptedData + encryptedIV
let encryptedKey = self.data.subdata(in: 0..<encKeyLength)
let encryptedData = self.data.subdata(in: encKeyLength..<encKeyLength+encryptedDataLength)
let encryptedIV = self.data.subdata(in: encKeyLength+encryptedDataLength..<self.data.count)
let rsaDecryptCtx = EVP_CIPHER_CTX_new_wrapper()
defer {
EVP_CIPHER_CTX_reset_wrapper(rsaDecryptCtx)
EVP_CIPHER_CTX_free_wrapper(rsaDecryptCtx)
}
EVP_CIPHER_CTX_set_padding(rsaDecryptCtx, padding)
// processedLen is the number of bytes that each EVP_DecryptUpdate/EVP_DecryptFinal decrypts.
// The sum of processedLen is the total size of the decrypted message (decMsgLen)
var processedLen: Int32 = 0
var decMsgLen: Int32 = 0
let decrypted = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(encryptedData.count + encryptedIV.count))
defer {
#if swift(>=4.1)
decrypted.deallocate()
#else
decrypted.deallocate(capacity: Int(encryptedData.count + encryptedIV.count))
#endif
}
// EVP_OpenInit returns 0 on error or the recovered secret key size if successful
var status = encryptedKey.withUnsafeBytes({ (ek: UnsafeRawBufferPointer) -> Int32 in
return encryptedIV.withUnsafeBytes({ (iv: UnsafeRawBufferPointer) -> Int32 in
return EVP_OpenInit(rsaDecryptCtx, .make(optional: encType), ek.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(encryptedKey.count), iv.baseAddress?.assumingMemoryBound(to: UInt8.self), .make(optional: key.reference))
})
})
guard status != 0 else {
let source = "Decryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_DECRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_DECRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
// EVP_OpenUpdate is a complex macros and therefore the compiler doesnt
// convert it directly to Swift. From /usr/local/opt/openssl/include/openssl/evp.h:
_ = encryptedData.withUnsafeBytes({ (enc: UnsafeRawBufferPointer) -> Int32 in
return EVP_DecryptUpdate(rsaDecryptCtx, decrypted, &processedLen, enc.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(encryptedData.count))
})
decMsgLen = processedLen
status = EVP_OpenFinal(rsaDecryptCtx, decrypted.advanced(by: Int(decMsgLen)), &processedLen)
guard status != 0 else {
let source = "Decryption failed"
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_DECRYPTION_FAILED, reason: reason)
}
throw Error(code: ERR_DECRYPTION_FAILED, reason: source + ": No OpenSSL error reported.")
}
decMsgLen += processedLen
return PlaintextData(with: Data(bytes: decrypted, count: Int(decMsgLen)))
}
#endif
// MARK: --- Sign/Verification
///
/// Sign the data
///
/// - 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, usePSS: Bool = false) throws -> SignedData? {
// Must be plaintext...
guard self.type == .plaintextType else {
throw Error(code: CryptorRSA.ERR_NOT_PLAINTEXT, reason: "Data is not plaintext")
}
// Key must be private...
guard key.type == .privateType else {
throw Error(code: CryptorRSA.ERR_KEY_NOT_PRIVATE, reason: "Supplied key is not private")
}
#if os(Linux)
let md_ctx = EVP_MD_CTX_new_wrapper()
defer {
EVP_MD_CTX_free_wrapper(md_ctx)
}
let (md, _) = algorithm.algorithmForSignature
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
return EVP_DigestUpdate(md_ctx, message.baseAddress?.assumingMemoryBound(to: UInt8.self), self.data.count)
})
// Determine the size of the actual signature
var sig_len: Int = 0
EVP_DigestSignFinal(md_ctx, nil, &sig_len)
let sig = UnsafeMutablePointer<UInt8>.allocate(capacity: sig_len)
defer {
#if swift(>=4.1)
sig.deallocate()
#else
sig.deallocate(capacity: sig_len)
#endif
}
let rc = EVP_DigestSignFinal(md_ctx, sig, &sig_len)
guard rc == 1, sig_len > 0 else {
let source = "Signing failed."
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_SIGNING_FAILED, reason: reason)
}
throw Error(code: ERR_SIGNING_FAILED, reason: source + ": No OpenSSL error reported.")
}
return SignedData(with: Data(bytes: sig, count: sig_len))
#else
let signingAlgorithm: SecKeyAlgorithm
if usePSS {
if #available(macOS 10.13, iOS 11.0, watchOS 4.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, signingAlgorithm, self.data as CFData, &response)
if response != nil {
guard let error = response?.takeRetainedValue() else {
throw Error(code: CryptorRSA.ERR_SIGNING_FAILED, reason: "Signing failed. Unable to determine error.")
}
throw Error(code: CryptorRSA.ERR_SIGNING_FAILED, reason: "Signing failed with error: \(error)")
}
return SignedData(with: sData! as Data)
#endif
}
///
/// Verify the signature
///
/// - Parameters:
/// - 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, usePSS: Bool = false) throws -> Bool {
// Must be plaintext...
guard self.type == .plaintextType else {
throw Error(code: CryptorRSA.ERR_NOT_PLAINTEXT, reason: "Data is not plaintext")
}
// Key must be public...
guard key.type == .publicType else {
throw Error(code: CryptorRSA.ERR_KEY_NOT_PRIVATE, reason: "Supplied key is not public")
}
// Signature must be signed data...
guard signature.type == .signedType else {
throw Error(code: CryptorRSA.ERR_NOT_SIGNED_DATA, reason: "Supplied signature is not of signed data type")
}
#if os(Linux)
let md_ctx = EVP_MD_CTX_new_wrapper()
defer {
EVP_MD_CTX_free_wrapper(md_ctx)
}
let (md, _) = algorithm.algorithmForSignature
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
return EVP_DigestUpdate(md_ctx, message.baseAddress?.assumingMemoryBound(to: UInt8.self), self.data.count)
})
guard rc == 1 else {
let source = "Signature verification failed."
if let reason = CryptorRSA.getLastError(source: source) {
throw Error(code: ERR_VERIFICATION_FAILED, reason: reason)
}
throw Error(code: ERR_VERIFICATION_FAILED, reason: source + ": No OpenSSL error reported.")
}
// Unlike other return values above, this return indicates if signature verifies or not
rc = signature.data.withUnsafeBytes({ (sig: UnsafeRawBufferPointer) -> Int32 in
// Wrapper for OpenSSL EVP_DigestVerifyFinal function defined in
// IBM-Swift/OpenSSL/shim.h, to provide compatibility with OpenSSL
// 1.0.1 and 1.0.2 on Ubuntu 14.04 and 16.04, respectively.
return SSL_EVP_digestVerifyFinal_wrapper(md_ctx, sig.baseAddress?.assumingMemoryBound(to: UInt8.self), signature.data.count)
})
return (rc == 1) ? true : false
#else
let signingAlgorithm: SecKeyAlgorithm
if usePSS {
if #available(macOS 10.13, iOS 11.0, watchOS 4.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, signingAlgorithm, self.data as CFData, signature.data as CFData, &response)
if response != nil {
guard let error = response?.takeRetainedValue() else {
throw Error(code: CryptorRSA.ERR_VERIFICATION_FAILED, reason: "Signature verification failed. Unable to determine error.")
}
throw Error(code: CryptorRSA.ERR_VERIFICATION_FAILED, reason: "Signature verification failed with error: \(error)")
}
return result
#endif
}
// MARK: --- Utility
///
/// Retrieve a digest of the data using the specified algorithm.
///
/// - Parameters:
/// - algorithm: Algoririthm to use.
///
/// - Returns: `Data` containing the digest.
///
public func digest(using algorithm: Data.Algorithm) throws -> Data {
return try self.data.digest(using: algorithm)
}
///
/// String representation of message in specified string encoding.
///
/// - Parameters:
/// - encoding: Encoding to use during the string conversion
///
/// - Returns: String representation of the message
///
public func string(using encoding: String.Encoding) throws -> String {
guard let str = String(data: data, encoding: encoding) else {
throw Error(code: CryptorRSA.ERR_STRING_ENCODING, reason: "Couldn't convert data to string representation")
}
return str
}
}
// MARK: -
///
/// Plaintext Data - Represents data not encrypted or signed.
///
public class PlaintextData: RSAData {
// MARK: Initializers
///
/// Initialize a new PlaintextData object.
///
/// - Parameters:
/// - data: `Data` containing the data.
///
/// - Returns: Newly initialized `PlaintextData`.
///
internal init(with data: Data) {
super.init(with: data, type: .plaintextType)
}
///
/// Creates a message from a plaintext string, with the specified encoding.
///
/// - Parameters:
/// - string: String value of the plaintext message
/// - encoding: Encoding to use to generate the clear data
///
/// - Returns: Newly initialized `RSAData`.
///
internal override init(with string: String, using encoding: String.Encoding) throws {
try super.init(with: string, using: encoding)
}
}
// MARK: -
///
/// Encrypted Data - Represents data encrypted.
///
public class EncryptedData: RSAData {
// MARK: Initializers
///
/// Initialize a new EncryptedData object.
///
/// - Parameters:
/// - data: `Data` containing the data.
///
/// - Returns: Newly initialized EncryptedData`.
///
public init(with data: Data) {
super.init(with: data, type: .encryptedType)
}
///
/// Creates a RSAData with a encrypted base64-encoded string.
///
/// - Parameters:
/// - base64String: Base64-encoded data of an encrypted message
///
/// - Returns: Newly initialized `RSAData`.
///
public override init(withBase64 base64String: String) throws {
try super.init(withBase64: base64String)
}
}
// MARK: -
///
/// Signed Data - Represents data that is signed.
///
public class SignedData: RSAData {
// MARK: -- Initializers
///
/// Initialize a new SignedData object.
///
/// - Parameters:
/// - data: `Data` containing the data.
///
/// - Returns: Newly initialized `SignedData`.
///
public init(with data: Data) {
super.init(with: data, type: .signedType)
}
}
}