Skip to content

Commit

Permalink
Merge pull request #6 from gtaban/linuxTest1
Browse files Browse the repository at this point in the history
Linux key support and associated tests passing OK
  • Loading branch information
billabt authored Dec 21, 2017
2 parents c88a556 + 22bd365 commit 471c095
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 101 deletions.
2 changes: 1 addition & 1 deletion Sources/CryptorRSA/CryptorRSAErrors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public extension CryptorRSA {
public static let ERR_EXTRACT_PUBLIC_KEY_FAILED = -9983
public static let ERR_EXTRACT_PRIVATE_KEY_FAILED = -9983
public static let ERR_NOT_IMPLEMENTED = -9982
// MARK: -- Error

///
Expand Down
159 changes: 119 additions & 40 deletions Sources/CryptorRSA/CryptorRSAKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,19 @@ public extension CryptorRSA {
// MARK: -- Public Key Creation

///
/// Creates a public key with data.
/// Creates a public key with DER data.
///
/// - Parameters:
/// - data: Key data
///
/// - Returns: New `PublicKey` instance.
///
public class func createPublicKey(with data: Data) throws -> PublicKey {

return try PublicKey(with: data)
#if os(Linux)
let data = CryptorRSA.convertDerToPem(from: data, type: .publicType)
#endif

return try PublicKey(with: data)
}

///
Expand All @@ -71,18 +74,23 @@ public extension CryptorRSA {
///
public class func createPublicKey(extractingFrom data: Data) throws -> PublicKey {

// Extact the data as a base64 string...
let str = String(data: data, encoding: .utf8)
guard let tmp = str else {

throw Error(code: ERR_CREATE_CERT_FAILED, reason: "Unable to create certificate from certificate data, incorrect format.")
}

let base64 = try CryptorRSA.base64String(for: tmp)
let data = Data(base64Encoded: base64)!

// Call the internal function to finish up...
return try CryptorRSA.createPublicKey(data: data)
#if os(Linux)
return try CryptorRSA.createPublicKey(data: data)
#else
// Extact the data as a base64 string...
let str = String(data: data, encoding: .utf8)
guard let tmp = str else {

throw Error(code: ERR_CREATE_CERT_FAILED, reason: "Unable to create certificate from certificate data, incorrect format.")
}

// Get the Base64 representation of the PEM encoded string after stripping off the PEM markers
let base64 = try CryptorRSA.base64String(for: tmp)
let data = Data(base64Encoded: base64)!

// Call the internal function to finish up...
return try CryptorRSA.createPublicKey(data: data)
#endif
}

///
Expand All @@ -95,12 +103,16 @@ public extension CryptorRSA {
///
public class func createPublicKey(withBase64 base64String: String) throws -> PublicKey {

guard let data = Data(base64Encoded: base64String, options: [.ignoreUnknownCharacters]) else {
guard var data = Data(base64Encoded: base64String, options: [.ignoreUnknownCharacters]) else {

throw Error(code: ERR_INIT_PK, reason: "Couldn't decode base64 string")
}

return try PublicKey(with: data)

#if os(Linux)
// OpenSSL uses the PEM version when importing key
data = CryptorRSA.convertDerToPem(from: data, type: .publicType)
#endif
return try PublicKey(with: data)
}

///
Expand All @@ -113,9 +125,18 @@ public extension CryptorRSA {
///
public class func createPublicKey(withPEM pemString: String) throws -> PublicKey {

let base64String = try CryptorRSA.base64String(for: pemString)

return try createPublicKey(withBase64: base64String)
#if os(Linux)
// openssl takes the full PEM format
let keyData = pemString.data(using: String.Encoding.utf8)!

return try PublicKey(with: keyData)

#else
// Get the Base64 representation of the PEM encoded string after stripping off the PEM markers
let base64String = try CryptorRSA.base64String(for: pemString)

return try createPublicKey(withBase64: base64String)
#endif
}

///
Expand Down Expand Up @@ -193,12 +214,18 @@ public extension CryptorRSA {
let fullPath = URL(fileURLWithPath: #file).appendingPathComponent( path.appending(certNameFull) ).standardized

// Import the data from the file...
let tmp = try String(contentsOf: fullPath)
let base64 = try CryptorRSA.base64String(for: tmp)
let data = Data(base64Encoded: base64)!

// Call the internal function to finish up...
return try CryptorRSA.createPublicKey(data: data)
#if os(Linux)
// In OpenSSL, we can just get the data and don't have to worry about stripping off headers etc.
let data = try Data(contentsOf: fullPath)
return try CryptorRSA.createPublicKey(data: data)
#else
// Get the Base64 representation of the PEM encoded string after stripping off the PEM markers
let tmp = try String(contentsOf: fullPath, encoding: .utf8)
let base64 = try CryptorRSA.base64String(for: tmp)
let data = Data(base64Encoded: base64)!

return try CryptorRSA.createPublicKey(data: data)
#endif
}

///
Expand Down Expand Up @@ -275,7 +302,7 @@ public extension CryptorRSA {
}

///
/// Creates a public key by extracting it certificate data.
/// Creates a public key by extracting it from certificate data.
///
/// - Parameters:
/// - data: `Data` representing the certificate.
Expand All @@ -286,11 +313,53 @@ public extension CryptorRSA {

#if os(Linux)

throw Error(code: ERR_NOT_IMPLEMENTED, reason: "Not implemented yet.")

let certbio = BIO_new(BIO_s_mem())
defer {
BIO_free(certbio)
}

// Move the key data to BIO
try data.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in

let len = BIO_write(certbio, buffer, Int32(data.count))
guard len != 0 else {
let source = "Couldn't create BIO 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.")
}

// The below is equivalent of BIO_flush...
BIO_ctrl(certbio, BIO_CTRL_FLUSH, 0, nil)
}
let cert = PEM_read_bio_X509(certbio, nil, nil, nil)

if (cert == nil) {
print("Error loading cert into memory\n")
throw Error(code: ERR_CREATE_CERT_FAILED, reason: "Error loading cert into memory.")
}

// Extract the certificate's public key data.
let evp_key = X509_get_pubkey(cert)
if ( evp_key == nil) {
throw Error(code: ERR_CREATE_CERT_FAILED, reason: "Error getting public key from certificate")
}

let key = EVP_PKEY_get1_RSA( evp_key)
if ( key == nil) {
throw Error(code: ERR_CREATE_CERT_FAILED, reason: "Error getting public key from certificate")
}
defer {
// RSA_free(key)
EVP_PKEY_free(evp_key)
}
return PublicKey(with: key!)

#else

// Create a certificate from the data...
// Create a DER-encoded X.509 certificate object from the DER data
let certificateData = SecCertificateCreateWithData(nil, data as CFData)
guard let certData = certificateData else {

Expand All @@ -300,7 +369,8 @@ public extension CryptorRSA {
// Now extract the public key from it...
var key: SecKey? = nil
let status: OSStatus = withUnsafeMutablePointer(to: &key) { ptr in


// Retrieves the public key from a certificate
SecCertificateCopyPublicKey(certData, UnsafeMutablePointer(ptr))
}
if status != errSecSuccess || key == nil {
Expand Down Expand Up @@ -356,9 +426,19 @@ public extension CryptorRSA {
///
public class func createPrivateKey(withPEM pemString: String) throws -> PrivateKey {

let base64String = try CryptorRSA.base64String(for: pemString)

return try CryptorRSA.createPrivateKey(withBase64: base64String)
#if os(Linux)
// openssl takes the full PEM format
let keyData = pemString.data(using: String.Encoding.utf8)!

return try PrivateKey(with: keyData)

#else
// SecKey needs the PEM format stripped off the header info and converted to base64

let base64String = try CryptorRSA.base64String(for: pemString)

return try CryptorRSA.createPrivateKey(withBase64: base64String)
#endif
}

///
Expand Down Expand Up @@ -491,7 +571,7 @@ public extension CryptorRSA {
// MARK: Initializers

///
/// Create a key using key data.
/// Create a key using key data (in DER format).
///
/// - Parameters:
/// - data: Key data.
Expand All @@ -502,10 +582,10 @@ public extension CryptorRSA {
internal init(with data: Data, type: KeyType) throws {

self.type = type
// On macOS, we need to strip off the header... Not so on Linux...

// On macOS, we need to strip off the X509 header if it exists.
#if !os(Linux)
let data = try CryptorRSA.stripPublicKeyHeader(for: data)
let data = try CryptorRSA.stripX509CertificateHeader(for: data)
#endif
reference = try CryptorRSA.createKey(from: data, type: type)
}
Expand Down Expand Up @@ -604,7 +684,6 @@ public extension CryptorRSA {
/// - Returns: New `PublicKey` instance.
///
public init(with data: Data) throws {

try super.init(with: data, type: .publicType)
}

Expand Down
77 changes: 41 additions & 36 deletions Sources/CryptorRSA/CryptorRSAUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ public extension CryptorRSA {

#if os(Linux)

/// Both the private and public key PEM read function take exactly the same parameters. This alias makes is easier to reference and use in code.
typealias RSAKeyReader = ((UnsafeMutablePointer<BIO>?, UnsafeMutablePointer<UnsafeMutablePointer<RSA>?>?, (@convention(c) (UnsafeMutablePointer<Int8>?, Int32, Int32, UnsafeMutableRawPointer?) -> Int32)?, UnsafeMutableRawPointer?) -> UnsafeMutablePointer<RSA>!)

///
/// Create a key from key data.
///
Expand All @@ -55,41 +52,49 @@ public extension CryptorRSA {

// Create a memory BIO...
let bio = BIO_new(BIO_s_mem())

defer {
BIO_free(bio)
}
defer {
BIO_free(bio)
}

// Move the key data to it...
keyData.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in
// create a BIO object with the key data
try keyData.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in

BIO_write(bio, buffer, Int32(keyData.count))

let len = BIO_write(bio, buffer, Int32(keyData.count))
guard len != 0 else {
let source = "Couldn't create BIO 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.")
}
// The below is equivalent of BIO_flush...
BIO_ctrl(bio, BIO_CTRL_FLUSH, 0, nil)

return
BIO_ctrl(bio, BIO_CTRL_FLUSH, 0, nil)
}

// It's base64 data...
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL)

// Get the right function depending on the type of key...
let keyReader: RSAKeyReader = type == .publicType ? PEM_read_bio_RSA_PUBKEY : PEM_read_bio_RSAPrivateKey

var evp_key: UnsafeMutablePointer<EVP_PKEY>
defer {
EVP_PKEY_free(evp_key)
}

// Read in the key data and process depending on key type
if type == .publicType {
evp_key = PEM_read_bio_PUBKEY(bio, nil, nil, nil)

} else {
evp_key = PEM_read_bio_PrivateKey(bio, nil, nil, nil)
}

let key = EVP_PKEY_get1_RSA( evp_key)
if ( key == nil ) {
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.")
}

// Read the key in...
let key = keyReader(bio, nil, nil, nil)

if key == nil {

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.")
}

return key!
}

Expand Down Expand Up @@ -166,7 +171,7 @@ public extension CryptorRSA {
static func createKey(from keyData: Data, type: CryptorRSA.RSAKey.KeyType) throws -> NativeKey {

var keyData = keyData
let keyClass = type == .publicType ? kSecAttrKeyClassPublic : kSecAttrKeyClassPrivate

let sizeInBits = keyData.count * MemoryLayout<UInt8>.size
Expand All @@ -180,7 +185,7 @@ public extension CryptorRSA {

throw Error(code: ERR_ADD_KEY, reason: "Couldn't create key reference from key data")
}
return key

}
Expand Down Expand Up @@ -222,7 +227,7 @@ public extension CryptorRSA {
///
/// - Returns: `Data` containing the public with header (if present) removed.
///
static func stripPublicKeyHeader(for keyData: Data) throws -> Data {
static func stripX509CertificateHeader(for keyData: Data) throws -> Data {

let count = keyData.count / MemoryLayout<CUnsignedChar>.size

Expand Down
Loading

0 comments on commit 471c095

Please sign in to comment.