Add passphrase parameter to ConfigurationParser
Use it to decrypt encrypted PEMs.
This commit is contained in:
parent
e5393f81b8
commit
b07ec88ff2
@ -114,25 +114,27 @@ public class ConfigurationParser {
|
|||||||
Parses an .ovpn file from an URL.
|
Parses an .ovpn file from an URL.
|
||||||
|
|
||||||
- Parameter url: The URL of the configuration file.
|
- Parameter url: The URL of the configuration file.
|
||||||
|
- Parameter passphrase: The optional passphrase for encrypted data.
|
||||||
- Parameter returnsStripped: When `true`, stores the stripped file into `ParsingResult.strippedLines`. Defaults to `false`.
|
- Parameter returnsStripped: When `true`, stores the stripped file into `ParsingResult.strippedLines`. Defaults to `false`.
|
||||||
- Returns: The `ParsingResult` outcome of the parsing.
|
- Returns: The `ParsingResult` outcome of the parsing.
|
||||||
- Throws: `ParsingError` if the configuration file is wrong or incomplete.
|
- Throws: `ParsingError` if the configuration file is wrong or incomplete.
|
||||||
*/
|
*/
|
||||||
public static func parsed(fromURL url: URL, returnsStripped: Bool = false) throws -> ParsingResult {
|
public static func parsed(fromURL url: URL, passphrase: String? = nil, returnsStripped: Bool = false) throws -> ParsingResult {
|
||||||
let lines = try String(contentsOf: url).trimmedLines()
|
let lines = try String(contentsOf: url).trimmedLines()
|
||||||
return try parsed(fromLines: lines, originalURL: url, returnsStripped: returnsStripped)
|
return try parsed(fromLines: lines, passphrase: passphrase, originalURL: url, returnsStripped: returnsStripped)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Parses an .ovpn file as an array of lines.
|
Parses an .ovpn file as an array of lines.
|
||||||
|
|
||||||
- Parameter lines: The array of lines holding the configuration.
|
- Parameter lines: The array of lines holding the configuration.
|
||||||
|
- Parameter passphrase: The optional passphrase for encrypted data.
|
||||||
- Parameter originalURL: The optional original URL of the configuration file.
|
- Parameter originalURL: The optional original URL of the configuration file.
|
||||||
- Parameter returnsStripped: When `true`, stores the stripped file into `ParsingResult.strippedLines`. Defaults to `false`.
|
- Parameter returnsStripped: When `true`, stores the stripped file into `ParsingResult.strippedLines`. Defaults to `false`.
|
||||||
- Returns: The `ParsingResult` outcome of the parsing.
|
- Returns: The `ParsingResult` outcome of the parsing.
|
||||||
- Throws: `ParsingError` if the configuration file is wrong or incomplete.
|
- Throws: `ParsingError` if the configuration file is wrong or incomplete.
|
||||||
*/
|
*/
|
||||||
public static func parsed(fromLines lines: [String], originalURL: URL? = nil, returnsStripped: Bool = false) throws -> ParsingResult {
|
public static func parsed(fromLines lines: [String], passphrase: String? = nil, originalURL: URL? = nil, returnsStripped: Bool = false) throws -> ParsingResult {
|
||||||
var strippedLines: [String]? = returnsStripped ? [] : nil
|
var strippedLines: [String]? = returnsStripped ? [] : nil
|
||||||
var warning: ParsingError? = nil
|
var warning: ParsingError? = nil
|
||||||
|
|
||||||
@ -209,10 +211,20 @@ public class ConfigurationParser {
|
|||||||
clientCertificate = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
clientCertificate = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
||||||
|
|
||||||
case "key":
|
case "key":
|
||||||
|
let isEncrypted = normalizeEncryptedPEMBlock(block: ¤tBlock)
|
||||||
let container = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
let container = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
||||||
clientKey = container
|
if isEncrypted {
|
||||||
if container.isEncrypted {
|
guard let passphrase = passphrase else {
|
||||||
unsupportedError = ParsingError.unsupportedConfiguration(option: "encrypted client certificate key")
|
unsupportedError = ParsingError.unsupportedConfiguration(option: "encrypted client certificate key (missing passphrase)")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
clientKey = try container.decrypted(with: passphrase)
|
||||||
|
} catch let e {
|
||||||
|
unsupportedError = ParsingError.unsupportedConfiguration(option: e.localizedDescription)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clientKey = container
|
||||||
}
|
}
|
||||||
|
|
||||||
case "tls-auth":
|
case "tls-auth":
|
||||||
@ -451,6 +463,16 @@ public class ConfigurationParser {
|
|||||||
warning: warning
|
warning: warning
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func normalizeEncryptedPEMBlock(block: inout [String]) -> Bool {
|
||||||
|
|
||||||
|
// XXX: restore blank line after encryption header (easier than tweaking trimmedLines)
|
||||||
|
if block.count >= 3 && block[1].contains("ENCRYPTED") {
|
||||||
|
block.insert("", at: 3)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension SocketType {
|
private extension SocketType {
|
||||||
@ -472,9 +494,3 @@ extension String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CryptoContainer {
|
|
||||||
var isEncrypted: Bool {
|
|
||||||
return pem.contains("ENCRYPTED")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import __TunnelKitNative
|
||||||
|
|
||||||
/// Represents a cryptographic container in PEM format.
|
/// Represents a cryptographic container in PEM format.
|
||||||
public struct CryptoContainer: Equatable {
|
public struct CryptoContainer: Equatable {
|
||||||
@ -73,3 +74,10 @@ extension CryptoContainer: Codable {
|
|||||||
try container.encode(pem)
|
try container.encode(pem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension CryptoContainer {
|
||||||
|
func decrypted(with passphrase: String) throws -> CryptoContainer {
|
||||||
|
let decryptedPEM = try TLSBox.decryptedPrivateKey(fromPEM: pem, passphrase: passphrase)
|
||||||
|
return CryptoContainer(pem: decryptedPEM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -80,6 +80,12 @@ class ConfigurationParserTests: XCTestCase {
|
|||||||
XCTAssertThrowsError(try ConfigurationParser.parsed(fromLines: lines))
|
XCTAssertThrowsError(try ConfigurationParser.parsed(fromLines: lines))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testEncryptedCertificateKey() throws {
|
||||||
|
let url = Bundle(for: ConfigurationParserTests.self).url(forResource: "tunnelbear", withExtension: "enc.ovpn")!
|
||||||
|
XCTAssertThrowsError(try ConfigurationParser.parsed(fromURL: url))
|
||||||
|
XCTAssertNoThrow(try ConfigurationParser.parsed(fromURL: url, passphrase: "foobar"))
|
||||||
|
}
|
||||||
|
|
||||||
private func url(withName name: String) -> URL {
|
private func url(withName name: String) -> URL {
|
||||||
return Bundle(for: ConfigurationParserTests.self).url(forResource: name, withExtension: "ovpn")!
|
return Bundle(for: ConfigurationParserTests.self).url(forResource: name, withExtension: "ovpn")!
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user