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.
|
||||
|
||||
- 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`.
|
||||
- Returns: The `ParsingResult` outcome of the parsing.
|
||||
- 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()
|
||||
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.
|
||||
|
||||
- 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 returnsStripped: When `true`, stores the stripped file into `ParsingResult.strippedLines`. Defaults to `false`.
|
||||
- Returns: The `ParsingResult` outcome of the parsing.
|
||||
- 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 warning: ParsingError? = nil
|
||||
|
||||
@ -209,10 +211,20 @@ public class ConfigurationParser {
|
||||
clientCertificate = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
||||
|
||||
case "key":
|
||||
let isEncrypted = normalizeEncryptedPEMBlock(block: ¤tBlock)
|
||||
let container = CryptoContainer(pem: currentBlock.joined(separator: "\n"))
|
||||
clientKey = container
|
||||
if container.isEncrypted {
|
||||
unsupportedError = ParsingError.unsupportedConfiguration(option: "encrypted client certificate key")
|
||||
if isEncrypted {
|
||||
guard let passphrase = passphrase else {
|
||||
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":
|
||||
@ -451,6 +463,16 @@ public class ConfigurationParser {
|
||||
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 {
|
||||
@ -472,9 +494,3 @@ extension String {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CryptoContainer {
|
||||
var isEncrypted: Bool {
|
||||
return pem.contains("ENCRYPTED")
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import __TunnelKitNative
|
||||
|
||||
/// Represents a cryptographic container in PEM format.
|
||||
public struct CryptoContainer: Equatable {
|
||||
@ -73,3 +74,10 @@ extension CryptoContainer: Codable {
|
||||
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))
|
||||
}
|
||||
|
||||
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 {
|
||||
return Bundle(for: ConfigurationParserTests.self).url(forResource: name, withExtension: "ovpn")!
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user