Skip to content

Commit

Permalink
Allow passing Order when creating CSR
Browse files Browse the repository at this point in the history
  • Loading branch information
m-barthelemy committed Apr 16, 2023
1 parent 1f40636 commit 73c38cb
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 3 deletions.
52 changes: 52 additions & 0 deletions Sources/AcmeSwift/APIs/AcmeSwift+Csr.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Foundation
import Crypto

extension AcmeSwift {
/// APIs related to CSRs.
public var csr: CsrAPI {
.init(client: self)
}

public struct CsrAPI {
fileprivate var client: AcmeSwift

/// Downloads the certificate chain for a finalized Order.
/// The certificates are returned a a list of PEM strings.
/// The first item is the final certificate for the domain.
/// The second item, if any, is the issuer certificate.
public func rsa(`for` order: AcmeOrderInfo) async throws -> [String] {
try await self.client.ensureLoggedIn()

guard order.status == .valid, let certURL = order.certificate else {
throw AcmeError.certificateNotReady(order.status, "Order must have a `valid` status. Some challenges might not have been completed yet")
}

let separator = "-----END CERTIFICATE-----\n"
let ep = DownloadCertificateEndpoint(certURL: certURL)
let (certificateChain, _) = try await self.client.run(ep, privateKey: self.client.login!.key, accountURL: client.accountURL!)
var certificates: [String] = []
for certificate in certificateChain.components(separatedBy: separator) {
if certificate != "" {
certificates.append("\(certificate)\(separator)".trimmingCharacters(in: .newlines))
}
}
return certificates
}

/// Revokes a previously issued certificate.
/// - Parameters:
/// - certificatePem: The Certificate **in PEM format**.
public func ecdsa(certificatePem: String, reason: AcmeRevokeReason? = nil) async throws {
try await self.client.ensureLoggedIn()

let csrBytes = certificatePem.pemToData()
let pemStr = csrBytes.toBase64UrlString()

let ep = RevokeCertificateEndpoint(
directory: self.client.directory,
spec: .init(certificate: pemStr, reason: reason)
)
let (_, _) = try await self.client.run(ep, privateKey: self.client.login!.key, accountURL: client.accountURL!)
}
}
}
40 changes: 37 additions & 3 deletions Sources/AcmeSwift/x509/AcmeX509Csr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,29 @@ public struct AcmeX509Csr {

return csr
}


public static func rsa(key: _CryptoExtras._RSA.Signing.PrivateKey = try! .init(keySize: .bits2048), subject: X509Subject? = nil, order: AcmeOrderInfo, keyUsage: X509KeyUsage? = nil, extendedKeyUsage: [X509ExtendedKeyUsageOID]? = nil) throws -> AcmeX509Csr {
guard order.identifiers.count > 0 else {
throw X509Error.noDomains("At least 1 DNS name is required")
}
let domains = order.identifiers.map{$0.value}
let rsa = try RsaCSR.init(key: key, subject: subject, domains: domains, keyUsage: keyUsage, extendedKeyUsage: extendedKeyUsage)
let csr = self.init(privateKey: rsa.key.derRepresentation, privateKeyPem: rsa.key.pemEncoded(), asn1Csr: rsa.asn1Csr)

return csr
}

public static func rsa(keyPem: String, subject: X509Subject? = nil, domains: [String]) throws -> AcmeX509Csr {
let key = try _CryptoExtras._RSA.Signing.PrivateKey.init(pemRepresentation: keyPem)
return try rsa(key: key, subject: subject, domains: domains)
}


public static func rsa(keyPem: String, subject: X509Subject? = nil, order: AcmeOrderInfo) throws -> AcmeX509Csr {
let key = try _CryptoExtras._RSA.Signing.PrivateKey.init(pemRepresentation: keyPem)
let domains = order.identifiers.map{$0.value}
return try rsa(key: key, subject: subject, domains: domains)
}

/// A CSR using a P256 private key
public static func ecdsa(key: Crypto.P256.Signing.PrivateKey = .init(), subject: X509Subject? = nil, domains: [String], keyUsage: X509KeyUsage? = nil, extendedKeyUsage: [X509ExtendedKeyUsageOID]? = nil) throws -> AcmeX509Csr {
guard domains.count > 0 else {
Expand All @@ -37,13 +54,30 @@ public struct AcmeX509Csr {
let ecdsa = try EcdsaCSR.init(key: key, subject: subject, domains: domains, keyUsage: keyUsage, extendedKeyUsage: extendedKeyUsage)
return self.init(privateKey: ecdsa.key.derRepresentation, privateKeyPem: ecdsa.key.pemEncoded(), asn1Csr: ecdsa.asn1Csr)
}


/// A CSR using a P256 private key
public static func ecdsa(key: Crypto.P256.Signing.PrivateKey = .init(), subject: X509Subject? = nil, order: AcmeOrderInfo, keyUsage: X509KeyUsage? = nil, extendedKeyUsage: [X509ExtendedKeyUsageOID]? = nil) throws -> AcmeX509Csr {
guard order.identifiers.count > 0 else {
throw X509Error.noDomains("At least 1 DNS name is required")
}
let domains = order.identifiers.map{$0.value}
let ecdsa = try EcdsaCSR.init(key: key, subject: subject, domains: domains, keyUsage: keyUsage, extendedKeyUsage: extendedKeyUsage)
return self.init(privateKey: ecdsa.key.derRepresentation, privateKeyPem: ecdsa.key.pemEncoded(), asn1Csr: ecdsa.asn1Csr)
}

/// A CSR using a P256 private key
public static func ecdsa(keyPem: String, subject: X509Subject? = nil, domains: [String]) throws -> AcmeX509Csr {
let key = try Crypto.P256.Signing.PrivateKey.init(pemRepresentation: keyPem)
return try ecdsa(key: key, subject: subject, domains: domains)
}

/// A CSR using a P256 private key
public static func ecdsa(keyPem: String, subject: X509Subject? = nil, order: AcmeOrderInfo) throws -> AcmeX509Csr {
let key = try Crypto.P256.Signing.PrivateKey.init(pemRepresentation: keyPem)
let domains = order.identifiers.map{$0.value}
return try ecdsa(key: key, subject: subject, domains: domains)
}

/// Returns the CSR as DER Data
public func derEncoded() throws -> Data {
let encoder = ASN1Encoder(schema: Asn1CertificateSigningRequest.schema)
Expand Down

0 comments on commit 73c38cb

Please sign in to comment.