Skip to content

Commit 520957b

Browse files
committed
Add support for post-quantum keys
1 parent 20276f5 commit 520957b

14 files changed

Lines changed: 691 additions & 63 deletions

File tree

.github/actions/setup/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ runs:
4545
# Install a 6.0 toolchain and SDK so we can package a static Linux binary
4646
# Used by setting TOOLCHAINS=swift in the environment
4747
# Once the macOS runner has a Swift 6 compiler, we can remove the toolchain download.
48-
- if: runner.os == 'macOS'
48+
- if: runner.os == 'macOS' && matrix.os != 'macos-26'
4949
run: |
5050
curl -L -s -o swift.pkg https://download.swift.org/swift-6.0-branch/xcode/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-09-17-a/swift-6.0-DEVELOPMENT-SNAPSHOT-2024-09-17-a-osx.pkg
5151
sudo installer -pkg swift.pkg -verbose -dumplog -target /

.github/workflows/build.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ jobs:
1616
build:
1717
strategy:
1818
matrix:
19-
os: [macos-14, ubuntu-22.04]
19+
os: [macos-14, macos-26, ubuntu-22.04]
2020
runs-on: ${{ matrix.os }}
2121
steps:
2222
- uses: actions/checkout@v4
2323
- uses: ./.github/actions/setup
24+
- if: matrix.os == 'macos-14' || matrix.os == 'ubuntu-22.04'
25+
run: make patch-package-swift-legacy
2426
- run: make
2527
- run: make test COVERAGE=1
2628
- run: make smoke-test-encrypt
@@ -43,7 +45,8 @@ jobs:
4345
env:
4446
TOOLCHAINS: swift
4547
ALPINE_KEY: ${{ secrets.ALPINE_KEY }}
46-
if: runner.os == 'macOS'
48+
# Having problems on latest macos, so or now limiting this to the older one
49+
if: runner.os == 'macOS' && matrix.os == 'macos-14'
4750
- name: Upload packages
4851
uses: actions/upload-artifact@v4
4952
with:
@@ -65,7 +68,7 @@ jobs:
6568
uses: actions/upload-pages-artifact@v3
6669
with:
6770
path: .build/site
68-
if: runner.os == 'macOS'
71+
if: runner.os == 'macOS' && matrix.os != 'macos-14'
6972

7073
# Running swift from the Makefile doesn't work (yet), so we have a separate
7174
# build procedure for windows.
@@ -74,6 +77,7 @@ jobs:
7477
steps:
7578
- uses: actions/checkout@v4
7679
- uses: ./.github/actions/setup
80+
- run: make patch-package-swift-legacy
7781
- run: swift build
7882
# Unit tests don't seem to work yet
7983
# - run: swift test

Documentation/age-plugin-se.1.scd

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ age-plugin-se - age plugin for Apple Secure Enclave
77

88
# SYNOPSIS
99

10-
*age-plugin-se* *keygen* \[*-o* _OUTPUT_\] \[*--access-control* _ACCESS_CONTROL_\]++
11-
*age-plugin-se* *recipients* \[*-o* _OUTPUT_\] \[*-i* _INPUT_\]
10+
*age-plugin-se* *keygen* \[*--pq*\] \[*-o* _OUTPUT_\] \[*--access-control* _ACCESS_CONTROL_\]++
11+
*age-plugin-se* *recipients* \[*--pq*\] \[*-o* _OUTPUT_\] \[*-i* _INPUT_\]
1212

1313

1414
# DESCRIPTION
@@ -48,6 +48,10 @@ output.
4848
*current-biometry*, *current-biometry-and-passcode*. Default:
4949
*any-biometry-or-passcode*.
5050

51+
*--pq*
52+
53+
Generate post-quantum keys
54+
5155

5256
# EXAMPLES
5357

Makefile

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ MAN_TARGET := man
4444
endif
4545

4646
.PHONY: all
47-
all: $(BUILD_DIR)/age-plugin-tag $(MAN_TARGET)
47+
all: $(BUILD_DIR)/age-plugin-tag $(BUILD_DIR)/age-plugin-tagpq $(MAN_TARGET)
4848
swift build $(SWIFT_BUILD_FLAGS)
4949

5050
.PHONY: package
@@ -100,17 +100,27 @@ ifneq ($(SCDOC),)
100100
install .build/age-plugin-se.1 $(DESTDIR)$(PREFIX)/share/man/man1
101101
endif
102102

103+
.IGNORE: .build/age-plugin-se.1
103104
man: .build/age-plugin-se.1
104105

105106
.build/age-plugin-se.1: Documentation/age-plugin-se.1.scd
106107
mkdir -p .build
107108
cat $< | sed "s/@VERSION@/$(VERSION)/g" | scdoc > $@.tmp
108109
mv $@.tmp $@
109110

110-
$(BUILD_DIR)/age-plugin-tag:
111+
$(BUILD_DIR)/age-plugin-tag $(BUILD_DIR)/age-plugin-tagpq:
111112
mkdir -p $(BUILD_DIR)
112113
ln -sf age-plugin-se $@
113114

115+
.PHONY: clean
116+
clean:
117+
-rm -rf .build manual-tests
118+
119+
patch-package-swift-legacy:
120+
cat Package.swift | sed -e 's/\/\/ swift-tools-version: .*/\/\/ swift-tools-version: 5.9/' -e 's/\.macOS(\.v26)/\.macOS(\.v14)/' > Package.swift.tmp
121+
mv Package.swift.tmp Package.swift
122+
123+
################################################################################
114124

115125
.PHONY: smoke-test
116126
smoke-test:
@@ -167,6 +177,17 @@ p256tag-decrypt-interop-test:
167177
$(AGE) --decrypt -i key.txt secret.txt.age && \
168178
rm -f key.txt secret.txt.age
169179

180+
.PHONY: mlkemp256tag-decrypt-interop-test
181+
mlkem768p256tag-decrypt-interop-test:
182+
$(AT)PATH="$(BUILD_DIR):$$PATH" && \
183+
$(ECHO) '\xf0\x9f\x94\x91 Generating key...' && \
184+
recipient=`age-plugin-se keygen --access-control=none --pq --recipient-type=tag -o key.txt | sed -e "s/Public key: //"` && \
185+
$(ECHO) '\xf0\x9f\x94\x92 Encrypting to '$$recipient'...' && \
186+
($(ECHO) '\xe2\x9c\x85 \x53\x75\x63\x63\x65\x73\x73' | $(AGE) --encrypt --recipient $$recipient -o secret.txt.age) && \
187+
$(ECHO) '\xf0\x9f\x94\x93 Decrypting...' && \
188+
$(AGE) --decrypt -i key.txt secret.txt.age && \
189+
rm -f key.txt secret.txt.age
190+
170191
.PHONY: gen-manual-tests
171192
gen-manual-tests:
172193
-rm -rf gen-manual-tests
@@ -185,9 +206,3 @@ run-manual-tests:
185206
$(AGE) --decrypt -i manual-tests/key.$$control.txt manual-tests/secret.txt.$$control.age; \
186207
$(ECHO) "\n-----\n"; \
187208
done
188-
189-
.PHONY: clean
190-
clean:
191-
-rm -rf .build manual-tests
192-
193-
.IGNORE: .build/age-plugin-se.1

Package.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
// swift-tools-version: 5.7
2-
1+
// swift-tools-version: 6.2
32
import PackageDescription
43

54
let package = Package(
65
name: "AgeSecureEnclavePlugin",
7-
platforms: [.macOS(.v13)],
6+
platforms: [.macOS(.v26)],
87
dependencies: [
98
// Only used on Linux & Windows
109
.package(url: "https://github.com/apple/swift-crypto.git", "2.0.0"..<"4.0.0")
@@ -14,7 +13,8 @@ let package = Package(
1413
name: "age-plugin-se",
1514
dependencies: [
1615
.product(
17-
name: "Crypto", package: "swift-crypto", condition: .when(platforms: [.linux, .windows]))
16+
name: "Crypto", package: "swift-crypto",
17+
condition: .when(platforms: [.linux, .windows]))
1818
],
1919
path: "Sources"),
2020
.testTarget(name: "Tests", dependencies: ["age-plugin-se"], path: "Tests"),

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ them with Touch ID.
2525
## Requirements
2626

2727
To generate identities (private keys) and decrypt encrypted files, you need a Mac
28-
running macOS 13 (Ventura) with a Secure Enclave processor.
28+
running macOS 14 (Sonoma) with a Secure Enclave processor.
2929

30-
For encrypting files, you need macOS 13 (Ventura), Linux, or Windows. A
30+
For encrypting files, you need macOS 14 (Sonoma), Linux, or Windows. A
3131
Secure Enclave processor is not necessary.
3232

3333
## Installation
@@ -244,8 +244,8 @@ $ age --decrypt -i key.txt data.tar.gz.age > data.tar.gz
244244

245245
## Usage
246246

247-
age-plugin-se keygen [-o OUTPUT] [--access-control ACCESS_CONTROL]
248-
age-plugin-se recipients [-o OUTPUT] [-i INPUT]
247+
age-plugin-se keygen [--pq] [-o OUTPUT] [--access-control ACCESS_CONTROL]
248+
age-plugin-se recipients [--pq] [-o OUTPUT] [-i INPUT]
249249

250250
The `keygen` subcommand generates a new private key bound to the current
251251
Secure Enclave, with the given access controls, and outputs it to OUTPUT
@@ -271,6 +271,7 @@ $ age --decrypt -i key.txt data.tar.gz.age > data.tar.gz
271271

272272
-i, --input INPUT Read data from the file at path INPUT
273273

274+
--pq Generate post-quantum keys
274275

275276

276277
## Development

Scripts/GenerateMLKEM768Keys.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/swift
2+
3+
import CryptoKit
4+
import Foundation
5+
6+
extension Data {
7+
func base64RawEncodedData() -> Data {
8+
var s = base64EncodedData(options: [])
9+
if let pi = s.firstIndex(of: Character("=").asciiValue!) {
10+
s = Data(s[s.startIndex..<pi])
11+
}
12+
return s
13+
}
14+
}
15+
16+
if #available(macOS 21.0, *) {
17+
for _ in 1...10 {
18+
let privateKey = try! CryptoKit.MLKEM768.PrivateKey()
19+
print(String(data: privateKey.seedRepresentation.base64RawEncodedData(), encoding: .utf8)!)
20+
}
21+
} else {
22+
print("ML-KEM is not supported on this operating system version.")
23+
}

Sources/CLI.swift

100644100755
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ struct CLI {
1616
case .keygen:
1717
let result = try plugin.generateKey(
1818
accessControl: options.accessControl.keyAccessControl,
19-
recipientType: options.recipientType.recipientType, now: Date())
19+
recipientType: options.recipientType.recipientType, now: Date(),
20+
pq: options.pq)
2021
if let outputFile = options.output {
2122
FileManager.default.createFile(
2223
atPath: outputFile,
@@ -94,6 +95,8 @@ struct Options {
9495
var output: String?
9596
var input: String?
9697

98+
var pq: Bool = false
99+
97100
enum AccessControl: String {
98101
case none = "none"
99102
case passcode = "passcode"
@@ -134,8 +137,8 @@ struct Options {
134137
static let help =
135138
"""
136139
Usage:
137-
age-plugin-se keygen [-o OUTPUT] [--access-control ACCESS_CONTROL]
138-
age-plugin-se recipients [-o OUTPUT] [-i INPUT]
140+
age-plugin-se keygen [--pq] [-o OUTPUT] [--access-control ACCESS_CONTROL]
141+
age-plugin-se recipients [--pq] [-o OUTPUT] [-i INPUT]
139142
140143
Description:
141144
The `keygen` subcommand generates a new private key bound to the current
@@ -163,6 +166,8 @@ struct Options {
163166
164167
-o, --output OUTPUT Write the result to the file at path OUTPUT
165168
169+
--pq Generate post-quantum keys
170+
166171
Example:
167172
$ age-plugin-se keygen -o key.txt
168173
Public key: age1se1qg8vwwqhztnh3vpt2nf2xwn7famktxlmp0nmkfltp8lkvzp8nafkqleh258
@@ -185,6 +190,8 @@ struct Options {
185190
} else if ["--version"].contains(arg) {
186191
opts.command = .version
187192
break
193+
} else if ["--pq"].contains(arg) {
194+
opts.pq = true
188195
} else if [
189196
"--age-plugin", "-i", "--input", "-o", "--output", "--access-control", "--recipient-type",
190197
].contains(where: {

0 commit comments

Comments
 (0)