File: SSLPKCS12Bundle.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (215 lines) | stat: -rw-r--r-- 8,628 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

#if compiler(>=5.1)
@_implementationOnly import CNIOBoringSSL
#else
import CNIOBoringSSL
#endif
import NIO


/// A container of a single PKCS#12 bundle.
///
/// PKCS#12 is a specification that defines an archive format for storing multiple
/// cryptographic objects together in one file. Its most common usage, and the one
/// that SwiftNIO is most interested in, is its use to bundle one or more X.509
/// certificates (`NIOSSLCertificate`) together with an associated private key
/// (`NIOSSLPrivateKey`).
///
/// ### Working with TLSConfiguration
///
/// In many cases users will want to configure a `TLSConfiguration` with the data
/// from a PKCS#12 bundle. This object assists in unpacking that bundle into its
/// associated pieces.
///
/// If you have a PKCS12 bundle, you configure a `TLSConfiguration` like this:
///
///     let p12Bundle = NIOSSLPKCS12Bundle(file: pathToMyP12)
///     let config = TLSConfiguration.makeServerConfiguration(
///         certificateChain: p12Bundle.certificateChain,
///         privateKey: p12Bundle.privateKey
///     )
///
/// The created `TLSConfiguration` can then be safely used for your endpoint.
public struct NIOSSLPKCS12Bundle {
    public let certificateChain: [NIOSSLCertificate]
    public let privateKey: NIOSSLPrivateKey

    private init<Bytes: Collection>(ref: OpaquePointer, passphrase: Bytes?) throws where Bytes.Element == UInt8 {
        var pkey: UnsafeMutablePointer<EVP_PKEY>? = nil
        var cert: UnsafeMutablePointer<X509>? = nil
        var caCerts: OpaquePointer? = nil

        let rc = try passphrase.withSecureCString { passphrase in
            CNIOBoringSSL_PKCS12_parse(ref, passphrase, &pkey, &cert, &caCerts)
        }
        guard rc == 1 else {
            throw BoringSSLError.unknownError(BoringSSLError.buildErrorStack())
        }

        // Successfully parsed, let's unpack. The key and cert are mandatory,
        // the ca stack is not.
        guard let actualCert = cert, let actualKey = pkey else {
            fatalError("Failed to obtain cert and pkey from a PKC12 file")
        }

        let certStackSize = caCerts.map { CNIOBoringSSL_sk_X509_num($0) } ?? 0
        var certs = [NIOSSLCertificate]()
        certs.reserveCapacity(Int(certStackSize) + 1)
        certs.append(NIOSSLCertificate.fromUnsafePointer(takingOwnership: actualCert))

        for idx in 0..<certStackSize {
            guard let stackCertPtr = CNIOBoringSSL_sk_X509_value(caCerts, idx) else {
                preconditionFailure("Unable to get cert \(idx) from stack \(String(describing: caCerts))")
            }
            certs.append(NIOSSLCertificate.fromUnsafePointer(takingOwnership: stackCertPtr))
        }

        self.certificateChain = certs
        self.privateKey = NIOSSLPrivateKey.fromUnsafePointer(takingOwnership: actualKey)

    }

    /// Create a `NIOSSLPKCS12Bundle` from the given bytes in memory,
    /// optionally decrypting the bundle with the given passphrase.
    ///
    /// - parameters:
    ///     - buffer: The bytes of the PKCS#12 bundle.
    ///     - passphrase: The passphrase used for the bundle, as a sequence of UTF-8 bytes.
    public init<Bytes: Collection>(buffer: [UInt8], passphrase: Bytes?) throws where Bytes.Element == UInt8 {
        guard boringSSLIsInitialized else { fatalError("Failed to initialize BoringSSL") }
        
        let p12 = buffer.withUnsafeBytes { pointer -> OpaquePointer? in
            let bio = CNIOBoringSSL_BIO_new_mem_buf(pointer.baseAddress, CInt(pointer.count))!
            defer {
                CNIOBoringSSL_BIO_free(bio)
            }
            return CNIOBoringSSL_d2i_PKCS12_bio(bio, nil)
        }
        defer {
            p12.map { CNIOBoringSSL_PKCS12_free($0) }
        }

        if let p12 = p12 {
            try self.init(ref: p12, passphrase: passphrase)
        } else {
            throw BoringSSLError.unknownError(BoringSSLError.buildErrorStack())
        }
    }

    /// Create a `NIOSSLPKCS12Bundle` from the given bytes on disk,
    /// optionally decrypting the bundle with the given passphrase.
    ///
    /// - parameters:
    ///     - file: The path to the PKCS#12 bundle on disk.
    ///     - passphrase: The passphrase used for the bundle, as a sequence of UTF-8 bytes.
    public init<Bytes: Collection>(file: String, passphrase: Bytes?) throws where Bytes.Element == UInt8 {
        guard boringSSLIsInitialized else { fatalError("Failed to initialize BoringSSL") }

        let fileObject = try Posix.fopen(file: file, mode: "rb")
        defer {
            fclose(fileObject)
        }

        let p12 = CNIOBoringSSL_d2i_PKCS12_fp(fileObject, nil)
        defer {
            p12.map(CNIOBoringSSL_PKCS12_free)
        }

        if let p12 = p12 {
            try self.init(ref: p12, passphrase: passphrase)
        } else {
            throw BoringSSLError.unknownError(BoringSSLError.buildErrorStack())
        }
    }

    /// Create a `NIOSSLPKCS12Bundle` from the given bytes on disk,
    /// assuming it has no passphrase.
    ///
    /// If the bundle does have a passphrase, call `init(file:passphrase:)` instead.
    ///
    /// - parameters:
    ///     - file: The path to the PKCS#12 bundle on disk.
    public init(file: String) throws {
        try self.init(file: file, passphrase: Optional<[UInt8]>.none)
    }

    /// Create a `NIOSSLPKCS12Bundle` from the given bytes in memory,
    /// assuming it has no passphrase.
    ///
    /// If the bundle does have a passphrase, call `init(buffer:passphrase:)` instead.
    ///
    /// - parameters:
    ///     - buffer: The bytes of the PKCS#12 bundle.
    public init(buffer: [UInt8]) throws {
        try self.init(buffer: buffer, passphrase: Optional<[UInt8]>.none)
    }
}


extension Collection where Element == UInt8 {
    /// Provides a contiguous copy of the bytes of this collection in a heap-allocated
    /// memory region that is locked into memory (that is, which can never be backed by a file),
    /// and which will be scrubbed and freed after use, and which is null-terminated.
    ///
    /// This method should be used when it is necessary to take a secure copy of a collection of
    /// bytes. Its implementation relies on BoringSSL directly.
    func withSecureCString<T>(_ block: (UnsafePointer<Int8>) throws -> T) throws -> T {
        // We need to allocate some memory and prevent it being swapped to disk while we use it.
        // For that reason we use mlock.
        let bufferSize = Int(self.count) + 1
        let bufferPtr = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: bufferSize)
        defer {
            bufferPtr.deallocate()
        }

        try Posix.mlock(addr: bufferPtr.baseAddress!, len: bufferPtr.count)
        defer {
            // If munlock fails take out the process.
            try! Posix.munlock(addr: bufferPtr.baseAddress!, len: bufferPtr.count)
        }

        let (_, nextIndex) = bufferPtr.initialize(from: self)
        assert(nextIndex == (bufferPtr.endIndex - 1))

        // Add a null terminator.
        bufferPtr[nextIndex] = 0

        defer {
            // We use OpenSSL_cleanse here because the compiler can't optimize this away.
            // .initialize(repeating: 0) can be, and empirically is, optimized away, bzero
            // is deprecated, memset_s is not well supported cross-platform, and memset-to-zero
            // is famously easily optimised away. This is our best bet.
            CNIOBoringSSL_OPENSSL_cleanse(bufferPtr.baseAddress!, bufferPtr.count)
            bufferPtr.baseAddress!.deinitialize(count: bufferPtr.count)
        }

        // Ok, the memory is ready for use. Call the user.
        return try bufferPtr.withMemoryRebound(to: Int8.self) {
            try block($0.baseAddress!)
        }
    }
}


internal extension Optional where Wrapped: Collection, Wrapped.Element == UInt8 {
    func withSecureCString<T>(_ block: (UnsafePointer<Int8>?) throws -> T) throws -> T {
        if let `self` = self {
            return try self.withSecureCString({ try block($0) })
        } else {
            return try block(nil)
        }
    }
}