Skip to content

Commit

Permalink
Merge branch 'main' into MahdiBM-patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
0xTim authored Nov 3, 2024
2 parents 0d56833 + cde5b8b commit 3eb99fe
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 144 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ DerivedData
Tests/LinuxMain.swift
.benchmarkBaselines/
Benchmarks/.benchmarkBaselines/
x5c_test_certs
72 changes: 36 additions & 36 deletions Tests/JWTKitTests/X5CTests.swift

Large diffs are not rendered by default.

54 changes: 0 additions & 54 deletions scripts/create-certs.sh

This file was deleted.

25 changes: 0 additions & 25 deletions scripts/create-jwt.py

This file was deleted.

29 changes: 0 additions & 29 deletions scripts/create-jwt.sh

This file was deleted.

65 changes: 65 additions & 0 deletions scripts/generate-certificates.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# This gets called by the `generateTokens.swift` script. Do not modify!

#!/usr/bin/env bash
set -e -o pipefail

# Configuration variables
COUNTRY="US"
STATE="New York"
CITY="New York"
ORGANIZATION="Vapor"
ORGANIZATIONAL_UNIT="Engineering"
EMAIL="admin@vapor.example.com"

mkdir -p jwt_x5c_certs
pushd jwt_x5c_certs

# Function to generate subject string
generate_subject() {
local prefix="$1"
echo "/C=$COUNTRY/ST=$STATE/L=$CITY/O=$ORGANIZATION/OU=$ORGANIZATIONAL_UNIT/CN=$ORGANIZATION $prefix/emailAddress=$EMAIL"
}

# Create Root Certificate
openssl ecparam -name prime256v1 -genkey -noout -out root_key.pem
openssl req -new -x509 -key root_key.pem -out root_cert.pem -days 3650 \
-subj "$(generate_subject 'Root CA')" \
-nodes

# Create Intermediate Certificate
openssl ecparam -name prime256v1 -genkey -noout -out intermediate_key.pem
openssl req -new -key intermediate_key.pem -out intermediate_csr.pem \
-subj "$(generate_subject 'Intermediate CA')" \
-nodes

echo 'basicConstraints=CA:TRUE' > intermediate_ext.txt
openssl x509 -req \
-in intermediate_csr.pem \
-CA root_cert.pem -CAkey root_key.pem \
-CAcreateserial \
-out intermediate_cert.pem \
-days 1825 \
-extfile intermediate_ext.txt

# Create Valid Leaf Certificate
openssl ecparam -name prime256v1 -genkey -noout -out leaf_key.pem
openssl req -new -key leaf_key.pem -out leaf_csr.pem \
-subj "$(generate_subject 'Leaf')" \
-nodes
openssl x509 -req -in leaf_csr.pem \
-CA intermediate_cert.pem -CAkey intermediate_key.pem \
-CAcreateserial \
-out leaf_cert.pem -days 365

# Create Expired Leaf Certificate
openssl ecparam -name prime256v1 -genkey -noout -out expired_leaf_key.pem
openssl req -new -key expired_leaf_key.pem -out expired_leaf_csr.pem \
-subj "$(generate_subject 'Expired Leaf')" \
-nodes
openssl x509 -req -in expired_leaf_csr.pem \
-CA intermediate_cert.pem -CAkey intermediate_key.pem \
-CAcreateserial \
-out expired_leaf_cert.pem -days -1

rm -f intermediate_ext.txt
popd
162 changes: 162 additions & 0 deletions scripts/generateTokens.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/// This script can be used to regenerate the X5CTests tokens
/// when they don't verify anymore. If you're here, it likely means
/// the certificates are expired and should be updated.
/// This script does just that: generate new certificates and create
/// tokens with x5c chains based on those certs.
///
/// To run the script, simply run `swift scripts/generateTokens.swift`.
/// The output will be
/// - the new tokens, printed in Swift, which means you
/// just need to copy and paste them replacing the old ones;
/// - the root certificate, which you have to replace too.
/// After creating the tokens, the script will delete certificates.
/// If you want to keep them for some reason, just add `--keep-certs`
/// to the script execution. They will be stored in the `x5c_test_certs`.
/// This directory is in the `.gitignore` so that it doesn't get committed.
import Foundation

enum ScriptError: Error {
case certificateGenerationFailed(status: Int32)
case fileNotFound(at: String)
case invalidSignature(String)
}

struct JWTGenerator {
let certificateDirectory = "x5c_test_certs"
let leafKeyFileName = "leaf_key.pem"
let leafCertFileName = "leaf_cert.pem"
let expiredLeafCertFileName = "expired_leaf_cert.pem"
let intermediateCertFileName = "intermediate_cert.pem"
let rootCertFileName = "root_cert.pem"

func generateCertificates() throws {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["scripts/generate-certificates.sh"]

try process.run()
process.waitUntilExit()

if process.terminationStatus != 0 {
throw ScriptError.certificateGenerationFailed(status: process.terminationStatus)
}

print("✅ Certificates generated successfully")
}

func readCertificateData(from name: String, stripped: Bool = true) throws -> String {
let path = URL(filePath: certificateDirectory).appending(path: name)
let content = try String(contentsOf: path, encoding: .utf8)
let finalContent =
if stripped {
content
.replacing("-----BEGIN CERTIFICATE-----", with: "")
.replacing("-----END CERTIFICATE-----", with: "")
.replacing("\n", with: "")
} else {
content
}
return finalContent
}

func generateToken(payload: [String: Any], certificates: [String], signingKeyPath: String) throws -> String {
func base64URLEncode(_ data: Data) -> String {
data.base64EncodedString()
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "=", with: "")
}

let x5cChain = try certificates.map { try readCertificateData(from: $0) }

let header: [String: Any] = [
"alg": "ES256",
"typ": "JWT",
"x5c": x5cChain,
]
let encodedHeader = try base64URLEncode(JSONSerialization.data(withJSONObject: header))
let encodedBody = try base64URLEncode(JSONSerialization.data(withJSONObject: payload))

let message = "\(encodedHeader).\(encodedBody)"

let task = Process()
task.executableURL = URL(filePath: "/bin/bash")
let command = """
echo -n "\(message)" |
openssl dgst -sha256 -sign "\(certificateDirectory)/\(signingKeyPath)" |
openssl asn1parse -inform DER |
perl -n -e '/INTEGER :([0-9A-Z]*)$/ && print $1' |
xxd -p -r
"""
task.arguments = ["-c", command]

let outputPipe = Pipe()
task.standardOutput = outputPipe

try task.run()
let signature = base64URLEncode(outputPipe.fileHandleForReading.readDataToEndOfFile())
return "\(message).\(signature)"
}

func generateTokens(keepingCertificates: Bool = false) throws {
let coolPayload = ["cool": true]
let tokens = try [
"validToken": generateToken(
payload: coolPayload, certificates: [leafCertFileName, intermediateCertFileName, rootCertFileName],
signingKeyPath: leafKeyFileName
),
"missingIntermediateToken": generateToken(
payload: coolPayload, certificates: [leafCertFileName, rootCertFileName], signingKeyPath: leafKeyFileName
),
"missingRootToken": generateToken(
payload: coolPayload, certificates: [leafCertFileName, intermediateCertFileName], signingKeyPath: leafKeyFileName
),
"missingLeafToken": generateToken(
payload: coolPayload, certificates: [intermediateCertFileName, rootCertFileName], signingKeyPath: leafKeyFileName
),
"missingLeafAndIntermediateToken": generateToken(
payload: coolPayload, certificates: [rootCertFileName], signingKeyPath: leafKeyFileName
),
"missingIntermediateAndRootToken": generateToken(
payload: coolPayload, certificates: [leafCertFileName], signingKeyPath: leafKeyFileName
),
"expiredLeafToken": generateToken(
payload: coolPayload, certificates: [expiredLeafCertFileName, intermediateCertFileName, rootCertFileName],
signingKeyPath: leafKeyFileName
),
"validButNotCoolToken": generateToken(
payload: ["cool": false], certificates: [leafCertFileName, intermediateCertFileName, rootCertFileName],
signingKeyPath: leafKeyFileName
),
]
print("Swift Token Declarations:")
for (name, token) in tokens {
print(
"""
let \(name) = \"""
\(token)
\"""
""")
}
try print(readCertificateData(from: rootCertFileName, stripped: false))

if !keepingCertificates {
try? FileManager.default.removeItem(atPath: certificateDirectory)
print("🧹 Certificates cleaned up")
} else {
if let absolutePath = FileManager.default.currentDirectoryPath as NSString? {
print("💾 Certificates saved in \(absolutePath.appendingPathComponent(certificateDirectory))")
}
}
}
}

let generator = JWTGenerator()
let keepingCertificates = CommandLine.arguments.contains("--keep-certs")
do {
try generator.generateCertificates()
try generator.generateTokens(keepingCertificates: keepingCertificates)
} catch {
print("Error: \(error)")
exit(1)
}

0 comments on commit 3eb99fe

Please sign in to comment.