File: DH.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 (196 lines) | stat: -rw-r--r-- 8,136 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
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
@_exported import CryptoKit
#else
import Foundation

/// A Diffie-Hellman Key Agreement Key
public protocol DiffieHellmanKeyAgreement {
    /// The public key share type to perform the DH Key Agreement
    associatedtype PublicKey
    var publicKey: PublicKey { get }

    /// Performs a Diffie-Hellman Key Agreement.
    ///
    /// - Parameters:
    ///   - publicKeyShare: The public key share.
    /// - Returns: The resulting key agreement result.
    func sharedSecretFromKeyAgreement(with publicKeyShare: PublicKey) throws -> SharedSecret
}

/// A key agreement result from which you can derive a symmetric cryptographic
/// key.
///
/// Generate a shared secret by calling your private key’s
/// `sharedSecretFromKeyAgreement(publicKeyShare:)` method with the public key
/// from another party. The other party computes the same secret by passing your
/// public key to the equivalent method on their own private key.
///
/// The shared secret isn’t suitable as a symmetric cryptographic key
/// (``SymmetricKey``) by itself. However, you use it to generate a key by
/// calling either the
/// ``hkdfDerivedSymmetricKey(using:salt:sharedInfo:outputByteCount:)`` or
/// ``x963DerivedSymmetricKey(using:sharedInfo:outputByteCount:)`` method of the
/// shared secret. After the other party does the same, then you both share a
/// symmetric key suitable for creating a message authentication code like
/// ``HMAC``, or for opening and closing a sealed box with a cipher like
/// ``ChaChaPoly`` or ``AES``.
public struct SharedSecret: ContiguousBytes {
    var ss: SecureBytes

    /// Invokes the given closure with a buffer pointer covering the raw bytes
    /// of the shared secret.
    ///
    /// - Parameters:
    ///   - body: A closure that takes a raw buffer pointer to the bytes of the
    /// shared secret and returns the shared secret.
    ///
    /// - Returns: The shared secret, as returned from the body closure.
    public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
        return try ss.withUnsafeBytes(body)
    }

    /// Derives a symmetric encryption key from the secret using x9.63 key
    /// derivation.
    ///
    /// - Parameters:
    ///   - hashFunction: The hash function to use for key derivation.
    ///   - sharedInfo: The shared information to use for key derivation.
    ///   - outputByteCount: The length in bytes of resulting symmetric key.
    ///
    /// - Returns: The derived symmetric key.
    public func x963DerivedSymmetricKey<H: HashFunction, SI: DataProtocol>(using hashFunction: H.Type, sharedInfo: SI, outputByteCount: Int) -> SymmetricKey {
        // SEC1 defines 3 inputs to the KDF:
        //
        // 1. An octet string Z which is the shared secret value. That's `self` here.
        // 2. An integer `keydatalen` which is the length in octets of the keying data to be generated. Here that's `outputByteCount`.
        // 3. An optional octet string `SharedInfo` which consists of other shared data. Here, that's `sharedInfo`.
        //
        // We then need to perform the following steps:
        //
        // 1. Check that keydatalen < hashlen × (2³² − 1). If keydatalen ≥ hashlen × (2³² − 1), fail.
        // 2. Initiate a 4 octet, big-endian octet string Counter as 0x00000001.
        // 3. For i = 1 to ⌈keydatalen/hashlen⌉, do the following:
        //     1. Compute: Ki = Hash(Z || Counter || [SharedInfo]).
        //     2. Increment Counter.
        //     3. Increment i.
        // 4. Set K to be the leftmost keydatalen octets of: K1 || K2 || . . . || K⌈keydatalen/hashlen⌉.
        // 5. Output K.
        //
        // The loop in step 3 is not very Swifty, so instead we generate the counter directly.
        // Step 1: Check that keydatalen < hashlen × (2³² − 1).
        // We do this math in UInt64-space, because we'll overflow 32-bit integers.
        guard UInt64(outputByteCount) < (UInt64(H.Digest.byteCount) * UInt64(UInt32.max)) else {
            fatalError("Invalid parameter size")
        }
        
        var key = SecureBytes()
        key.reserveCapacity(outputByteCount)
        
        var remainingBytes = outputByteCount
        var counter = UInt32(1)
        
        while remainingBytes > 0 {
            // 1. Compute: Ki = Hash(Z || Counter || [SharedInfo]).
            var hasher = H()
            hasher.update(self)
            hasher.update(counter.bigEndian)
            hasher.update(data: sharedInfo)
            let digest = hasher.finalize()
            
            // 2. Increment Counter.
            counter += 1
            
            // Append the bytes of the digest. We don't want to append more than the remaining number of bytes.
            let bytesToAppend = min(remainingBytes, H.Digest.byteCount)
            digest.withUnsafeBytes { digestPtr in
                key.append(digestPtr.prefix(bytesToAppend))
            }
            remainingBytes -= bytesToAppend
        }
        
        precondition(key.count == outputByteCount)
        return SymmetricKey(data: key)
    }

    /// Derives a symmetric encryption key from the secret using HKDF key
    /// derivation.
    ///
    /// - Parameters:
    ///   - hashFunction: The hash function to use for key derivation.
    ///   - salt: The salt to use for key derivation.
    ///   - sharedInfo: The shared information to use for key derivation.
    ///   - outputByteCount: The length in bytes of resulting symmetric key.
    ///
    /// - Returns: The derived symmetric key.
    public func hkdfDerivedSymmetricKey<H: HashFunction, Salt: DataProtocol, SI: DataProtocol>(using hashFunction: H.Type, salt: Salt, sharedInfo: SI, outputByteCount: Int) -> SymmetricKey {
        #if os(iOS) && (arch(arm) || arch(i386))
        fatalError("Unsupported architecture")
        #else
        return HKDF<H>.deriveKey(inputKeyMaterial: SymmetricKey(data: ss), salt: salt, info: sharedInfo, outputByteCount: outputByteCount)
        #endif
    }
}

extension SharedSecret: Hashable {
    public func hash(into hasher: inout Hasher) {
        ss.withUnsafeBytes { hasher.combine(bytes: $0) }
    }
}

// We want to implement constant-time comparison for digests.
extension SharedSecret: CustomStringConvertible, Equatable {
    public static func == (lhs: Self, rhs: Self) -> Bool {
        return safeCompare(lhs, rhs)
    }
    
    /// Determines whether a shared secret is equivalent to a collection of
    /// contiguous bytes.
    ///
    /// - Parameters:
    ///   - lhs: The shared secret to compare.
    ///   - rhs: A collection of contiguous bytes to compare.
    ///
    /// - Returns: A Boolean value that’s `true` if the shared secret and the
    /// collection of binary data are equivalent.
    public static func == <D: DataProtocol>(lhs: Self, rhs: D) -> Bool {
        if rhs.regions.count != 1 {
            let rhsContiguous = Data(rhs)
            return safeCompare(lhs, rhsContiguous)
        } else {
            return safeCompare(lhs, rhs.regions.first!)
        }
    }

    public var description: String {
        return "\(Self.self): \(ss.hexString)"
    }
}

extension HashFunction {
    // A wrapper function to keep the unsafe code in one place.
    mutating func update(_ secret: SharedSecret) {
        secret.withUnsafeBytes {
            self.update(bufferPointer: $0)
        }
    }
    mutating func update(_ counter: UInt32) {
        withUnsafeBytes(of: counter) {
            self.update(bufferPointer: $0)
        }
    }
}

#endif // Linux or !SwiftPM