1import Crypto
2import Foundation
3
4struct ECPublicJWK: Codable {
5 var kty = "EC"
6 var crv = "P-256"
7 var x: String
8 var y: String
9}
10
11func base64URL(_ data: Data) -> String {
12 data.base64EncodedString()
13 .replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_").replacingOccurrences(of: "=", with: "")
14}
15
16func base64URLDecode(_ string: String) -> Data? {
17 var base64 = string.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
18 base64 += String(repeating: "=", count: (4 - base64.count % 4) % 4)
19 return Data(base64Encoded: base64)
20}
21
22// PEM -> JWK: rawRepresentation is x ‖ y, 32 bytes each
23let key = try P256.Signing.PublicKey(pemRepresentation: String(contentsOfFile: "pub.pem", encoding: .utf8))
24let point = key.rawRepresentation
25let jwk = ECPublicJWK(x: base64URL(point.prefix(32)), y: base64URL(point.suffix(32)))
26let json = try JSONEncoder().encode(jwk)
27try json.write(to: URL(fileURLWithPath: "out.jwk.json"))
28print(String(decoding: json, as: UTF8.self))
29
30// JWK -> PEM: init(rawRepresentation:) validates the point, pemRepresentation writes SPKI
31let input = try JSONDecoder().decode(ECPublicJWK.self, from: Data(contentsOf: URL(fileURLWithPath: "pub.jwk.json")))
32guard input.kty == "EC", input.crv == "P-256", let x = base64URLDecode(input.x), let y = base64URLDecode(input.y) else {
33 fatalError("not a P-256 public JWK")
34}
35let pem = try P256.Signing.PublicKey(rawRepresentation: x + y).pemRepresentation + "\n"
36try pem.write(toFile: "out.pem", atomically: true, encoding: .utf8)
37print(pem, terminator: "")