Skip to content

Commit 4c77c73

Browse files
authored
Initial Swift API for PEM format (#453)
* Initial Swift API for PEM format * Created PEM keys from OpenSSL for unit tests * Updated readme
1 parent 34b378b commit 4c77c73

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+708
-8
lines changed

Diff for: .swiftlint.yml

+12
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,19 @@ included: # paths to include during linting. `--path` is ignored if present.
132132
- Sources
133133
- Tests
134134
excluded: # paths to ignore during linting. Takes precedence over `included`.
135+
- Sources/**/ArraySliceBigint.swift
136+
- Sources/**/ASN1.swift
137+
- Sources/**/ASN1BitString.swift
138+
- Sources/**/ASN1Boolean.swift
139+
- Sources/**/ASN1Identifier.swift
140+
- Sources/**/ASN1Integer.swift
141+
- Sources/**/ASN1OctetString.swift
142+
- Sources/**/ASN1Strings.swift
135143
- Sources/**/Digest.swift
144+
- Sources/**/ECDSASignature.swift
145+
- Sources/**/GeneralizedTime.swift
146+
- Sources/**/PEMDocument.swift
147+
- Sources/**/PKCS8PrivateKey.swift
136148
- Sources/**/PrettyBytes.swift
137149
- Sources/**/RNG_boring.swift
138150
- Sources/**/SecureBytes.swift

Diff for: README.md

+17-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Long-term goals are:
2020
This repository primarily uses Swift package manager as its build tool, so we recommend using that as well. Xcode comes with [built-in support](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) for Swift packages. From the menu bar, goto: `File > Add Packages...` If you manage packages via a `Package.swift` file, simply add `secp256k1.swift` as a dependencies' clause in your Swift manifest:
2121

2222
```swift
23-
.package(name: "secp256k1.swift", url: "https://github.com/GigaBitcoin/secp256k1.swift.git", exact: "0.13.0"),
23+
.package(name: "secp256k1.swift", url: "https://github.com/GigaBitcoin/secp256k1.swift.git", exact: "0.15.0"),
2424
```
2525

2626
Include `secp256k1` as a dependency for your executable target:
@@ -136,7 +136,7 @@ let publicKey = try! secp256k1.Recovery.PublicKey(messageData, signature: recove
136136
let signature = try! recoverySignature.normalize
137137
```
138138

139-
# Combine Public Keys
139+
## Combine Public Keys
140140

141141
```swift
142142
let privateKey = try! secp256k1.Signing.PrivateKey()
@@ -146,6 +146,21 @@ let publicKey = try! secp256k1.Signing.PrivateKey().public
146146
publicKey.combine([privateKey.publicKey], format: .uncompressed)
147147
```
148148

149+
## PEM Key Format
150+
151+
```swift
152+
let privateKeyString = """
153+
-----BEGIN EC PRIVATE KEY-----
154+
MHQCAQEEIBXwHPDpec6b07GeLbnwetT0dvWzp0nV3MR+4pPKXIc7oAcGBSuBBAAK
155+
oUQDQgAEt2uDn+2GqqYs/fmkBr5+rCQ3oiFSIJMAcjHIrTDS6HEELgguOatmFBOp
156+
2wU4P2TAl/0Ihiq+nMkrAIV69m2W8g==
157+
-----END EC PRIVATE KEY-----
158+
"""
159+
160+
// Import keys generated from OpenSSL
161+
let privateKey = try! secp256k1.Signing.PrivateKey(pemRepresentation: privateKeyString)
162+
```
163+
149164

150165
# Danger
151166
These APIs should not be considered stable and may change at any time.

Diff for: Sources/secp256k1/ASN1/ASN1.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Submodules/swift-crypto/Sources/Crypto/ASN1/ASN1.swift
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Any.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1BitString.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Boolean.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Identifier.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Integer.swift
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Null.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1OctetString.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Strings.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ArraySliceBigint.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/GeneralizedTime.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../zkp/ASN1/Basic ASN1 Types/ObjectIdentifier.swift

Diff for: Sources/secp256k1/ASN1/ECDSASignature.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Submodules/swift-crypto/Sources/Crypto/ASN1/ECDSASignature.swift

Diff for: Sources/secp256k1/ASN1/PEMDocument.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Submodules/swift-crypto/Sources/Crypto/ASN1/PEMDocument.swift

Diff for: Sources/secp256k1/ASN1/PKCS8PrivateKey.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Submodules/swift-crypto/Sources/Crypto/ASN1/PKCS8PrivateKey.swift

Diff for: Sources/secp256k1/ASN1/SEC1PrivateKey.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../zkp/ASN1/SEC1PrivateKey.swift

Diff for: Sources/secp256k1/ASN1/SubjectPublicKeyInfo.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../zkp/ASN1/SubjectPublicKeyInfo.swift

Diff for: Sources/secp256k1/CryptoKitErrors.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../Submodules/swift-crypto/Sources/Crypto/CryptoKitErrors.swift

Diff for: Sources/zkp/ASN1/ASN1.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Submodules/swift-crypto/Sources/Crypto/ASN1/ASN1.swift

Diff for: Sources/zkp/ASN1/Basic ASN1 Types/ASN1Any.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Any.swift
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1BitString.swift

Diff for: Sources/zkp/ASN1/Basic ASN1 Types/ASN1Boolean.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Boolean.swift
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Identifier.swift

Diff for: Sources/zkp/ASN1/Basic ASN1 Types/ASN1Integer.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Integer.swift

Diff for: Sources/zkp/ASN1/Basic ASN1 Types/ASN1Null.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Null.swift
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1OctetString.swift

Diff for: Sources/zkp/ASN1/Basic ASN1 Types/ASN1Strings.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ASN1Strings.swift
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/ArraySliceBigint.swift
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../Submodules/swift-crypto/Sources/Crypto/ASN1/Basic ASN1 Types/GeneralizedTime.swift
+231
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
//
2+
// ObjectIdentifier.swift
3+
// GigaBitcoin/secp256k1.swift
4+
//
5+
// Modifications Copyright (c) 2023 GigaBitcoin LLC
6+
// Distributed under the MIT software license
7+
//
8+
// See the accompanying file LICENSE for information
9+
//
10+
//
11+
// NOTICE: THIS FILE HAS BEEN MODIFIED BY GigaBitcoin LLC
12+
// UNDER COMPLIANCE WITH THE APACHE 2.0 LICENSE FROM THE
13+
// ORIGINAL WORK OF THE COMPANY Apple Inc.
14+
//
15+
// THE FOLLOWING IS THE COPYRIGHT OF THE ORIGINAL DOCUMENT:
16+
//
17+
//
18+
//===----------------------------------------------------------------------===//
19+
//
20+
// This source file is part of the SwiftCrypto open source project
21+
//
22+
// Copyright (c) 2019-2020 Apple Inc. and the SwiftCrypto project authors
23+
// Licensed under Apache License v2.0
24+
//
25+
// See LICENSE.txt for license information
26+
// See CONTRIBUTORS.md for the list of SwiftCrypto project authors
27+
//
28+
// SPDX-License-Identifier: Apache-2.0
29+
//
30+
//===----------------------------------------------------------------------===//
31+
#if CRYPTO_IN_SWIFTPM && !CRYPTO_IN_SWIFTPM_FORCE_BUILD_API
32+
@_exported import CryptoKit
33+
#else
34+
import Foundation
35+
36+
extension ASN1 {
37+
/// An Object Identifier is a representation of some kind of object: really any kind of object.
38+
///
39+
/// It represents a node in an OID hierarchy, and is usually represented as an ordered sequence of numbers.
40+
///
41+
/// We mostly don't care about the semantics of the thing, we just care about being able to store and compare them.
42+
struct ASN1ObjectIdentifier: ASN1ImplicitlyTaggable {
43+
static var defaultIdentifier: ASN1.ASN1Identifier {
44+
.objectIdentifier
45+
}
46+
47+
private var oidComponents: [UInt]
48+
49+
init(asn1Encoded node: ASN1.ASN1Node, withIdentifier identifier: ASN1.ASN1Identifier) throws {
50+
guard node.identifier == identifier else {
51+
throw CryptoKitASN1Error.unexpectedFieldType
52+
}
53+
54+
guard case var .primitive(content) = node.content else {
55+
preconditionFailure("ASN.1 parser generated primitive node with constructed content")
56+
}
57+
58+
// We have to parse the content. From the spec:
59+
//
60+
// > Each subidentifier is represented as a series of (one or more) octets. Bit 8 of each octet indicates whether it
61+
// > is the last in the series: bit 8 of the last octet is zero, bit 8 of each preceding octet is one. Bits 7 to 1 of
62+
// > the octets in the series collectively encode the subidentifier. Conceptually, these groups of bits are concatenated
63+
// > to form an unsigned binary number whose most significant bit is bit 7 of the first octet and whose least significant
64+
// > bit is bit 1 of the last octet. The subidentifier shall be encoded in the fewest possible octets[...].
65+
// >
66+
// > The number of subidentifiers (N) shall be one less than the number of object identifier components in the object identifier
67+
// > value being encoded.
68+
// >
69+
// > The numerical value of the first subidentifier is derived from the values of the first _two_ object identifier components
70+
// > in the object identifier value being encoded, using the formula:
71+
// >
72+
// > (X*40) + Y
73+
// >
74+
// > where X is the value of the first object identifier component and Y is the value of the second object identifier component.
75+
//
76+
// Yeah, this is a bit bananas, but basically there are only 3 first OID components (0, 1, 2) and there are no more than 39 children
77+
// of nodes 0 or 1. In my view this is too clever by half, but the ITU.T didn't ask for my opinion when they were coming up with this
78+
// scheme, likely because I was in middle school at the time.
79+
var subcomponents = [UInt]()
80+
while content.count > 0 {
81+
try subcomponents.append(content.readOIDSubidentifier())
82+
}
83+
84+
guard subcomponents.count >= 2 else {
85+
throw CryptoKitASN1Error.invalidObjectIdentifier
86+
}
87+
88+
// Now we need to expand the subcomponents out. This means we need to undo the step above. The first component will be in the range 0..<40
89+
// when the first oidComponent is 0, 40..<80 when the first oidComponent is 1, and 80+ when the first oidComponent is 2.
90+
var oidComponents = [UInt]()
91+
oidComponents.reserveCapacity(subcomponents.count + 1)
92+
93+
switch subcomponents.first! {
94+
case ..<40:
95+
oidComponents.append(0)
96+
oidComponents.append(subcomponents.first!)
97+
98+
case 40..<80:
99+
oidComponents.append(1)
100+
oidComponents.append(subcomponents.first! - 40)
101+
102+
default:
103+
oidComponents.append(2)
104+
oidComponents.append(subcomponents.first! - 80)
105+
}
106+
107+
oidComponents.append(contentsOf: subcomponents.dropFirst())
108+
109+
self.oidComponents = oidComponents
110+
}
111+
112+
func serialize(into coder: inout ASN1.Serializer, withIdentifier identifier: ASN1.ASN1Identifier) throws {
113+
coder.appendPrimitiveNode(identifier: identifier) { bytes in
114+
var components = self.oidComponents[...]
115+
guard let firstComponent = components.popFirst(), let secondComponent = components.popFirst() else {
116+
preconditionFailure("Invalid number of OID components: must be at least two!")
117+
}
118+
119+
let serializedFirstComponent = (firstComponent * 40) + secondComponent
120+
Self.writeOIDSubidentifier(serializedFirstComponent, into: &bytes)
121+
122+
while let component = components.popFirst() {
123+
Self.writeOIDSubidentifier(component, into: &bytes)
124+
}
125+
}
126+
}
127+
128+
private static func writeOIDSubidentifier(_ identifier: UInt, into array: inout [UInt8]) {
129+
// An OID subidentifier is written as an integer over 7-bit bytes, where the last byte has the top bit unset.
130+
// The first thing we need is to know how many bits we need to write
131+
let bitsToWrite = UInt.bitWidth - identifier.leadingZeroBitCount
132+
let bytesToWrite = (bitsToWrite + 6) / 7
133+
134+
guard bytesToWrite > 0 else {
135+
// Just a zero.
136+
array.append(0)
137+
return
138+
}
139+
140+
for byteNumber in (1..<bytesToWrite).reversed() {
141+
let shift = byteNumber * 7
142+
let byte = UInt8((identifier >> shift) & 0x7F) | 0x80
143+
array.append(byte)
144+
}
145+
146+
// Last byte to append here, we must unset the top bit.
147+
let byte = UInt8(identifier & 0x7F)
148+
array.append(byte)
149+
}
150+
}
151+
}
152+
153+
extension ASN1.ASN1ObjectIdentifier: Hashable {}
154+
155+
extension ASN1.ASN1ObjectIdentifier: ExpressibleByArrayLiteral {
156+
init(arrayLiteral elements: UInt...) {
157+
self.oidComponents = elements
158+
}
159+
}
160+
161+
extension ASN1.ASN1ObjectIdentifier {
162+
enum NamedCurves {
163+
static let secp256k1: ASN1.ASN1ObjectIdentifier = [1, 3, 132, 0, 10]
164+
}
165+
166+
enum HashFunctions {
167+
static let sha256: ASN1.ASN1ObjectIdentifier = [2, 16, 840, 1, 101, 3, 4, 2, 1]
168+
static let sha384: ASN1.ASN1ObjectIdentifier = [2, 16, 840, 1, 101, 3, 4, 2, 2]
169+
static let sha512: ASN1.ASN1ObjectIdentifier = [2, 16, 840, 1, 101, 3, 4, 2, 3]
170+
}
171+
172+
enum AlgorithmIdentifier {
173+
static let idEcPublicKey: ASN1.ASN1ObjectIdentifier = [1, 2, 840, 10045, 2, 1]
174+
}
175+
176+
enum NameAttributes {
177+
static let name: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 41]
178+
static let surname: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 4]
179+
static let givenName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 42]
180+
static let initials: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 43]
181+
static let generationQualifier: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 44]
182+
static let commonName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 3]
183+
static let localityName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 7]
184+
static let stateOrProvinceName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 8]
185+
static let organizationName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 10]
186+
static let organizationalUnitName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 11]
187+
static let title: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 12]
188+
static let dnQualifier: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 46]
189+
static let countryName: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 6]
190+
static let serialNumber: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 5]
191+
static let pseudonym: ASN1.ASN1ObjectIdentifier = [2, 5, 4, 65]
192+
static let domainComponent: ASN1.ASN1ObjectIdentifier = [0, 9, 2342, 19200300, 100, 1, 25]
193+
static let emailAddress: ASN1.ASN1ObjectIdentifier = [1, 2, 840, 113549, 1, 9, 1]
194+
}
195+
}
196+
197+
fileprivate extension ArraySlice where Element == UInt8 {
198+
mutating func readOIDSubidentifier() throws -> UInt {
199+
// In principle OID subidentifiers can be too large to fit into a UInt. We are choosing to not care about that
200+
// because for us it shouldn't matter.
201+
guard let subidentifierEndIndex = firstIndex(where: { $0 & 0x80 == 0x00 }) else {
202+
throw CryptoKitASN1Error.invalidASN1Object
203+
}
204+
205+
let oidSlice = self[startIndex...subidentifierEndIndex]
206+
self = self[index(after: subidentifierEndIndex)...]
207+
208+
// We need to compact the bits. These are 7-bit integers, which is really awkward.
209+
return try UInt(sevenBitBigEndianBytes: oidSlice)
210+
}
211+
}
212+
213+
fileprivate extension UInt {
214+
init<Bytes: Collection>(sevenBitBigEndianBytes bytes: Bytes) throws where Bytes.Element == UInt8 {
215+
// We need to know how many bytes we _need_ to store this "int".
216+
guard ((bytes.count * 7) + 7) / 8 <= MemoryLayout<UInt>.size else {
217+
throw CryptoKitASN1Error.invalidASN1Object
218+
}
219+
220+
self = 0
221+
let shiftSizes = stride(from: 0, to: bytes.count * 7, by: 7).reversed()
222+
223+
var index = bytes.startIndex
224+
for shift in shiftSizes {
225+
self |= UInt(bytes[index] & 0x7F) << shift
226+
bytes.formIndex(after: &index)
227+
}
228+
}
229+
}
230+
231+
#endif // Linux or !SwiftPM

Diff for: Sources/zkp/ASN1/ECDSASignature.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Submodules/swift-crypto/Sources/Crypto/ASN1/ECDSASignature.swift

Diff for: Sources/zkp/ASN1/PEMDocument.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Submodules/swift-crypto/Sources/Crypto/ASN1/PEMDocument.swift

Diff for: Sources/zkp/ASN1/PKCS8PrivateKey.swift

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../Submodules/swift-crypto/Sources/Crypto/ASN1/PKCS8PrivateKey.swift

0 commit comments

Comments
 (0)