diff --git a/Sources/ShieldX509/CertificateBuilder.swift b/Sources/ShieldX509/CertificateBuilder.swift index e50995251..00123e3e1 100644 --- a/Sources/ShieldX509/CertificateBuilder.swift +++ b/Sources/ShieldX509/CertificateBuilder.swift @@ -145,8 +145,7 @@ public extension Certificate { var extensions = self.extensions if let keyUsage = keyUsage { extensions = extensions ?? Extensions() - extensions!.remove(KeyUsage.self) - try extensions!.append(value: keyUsage) + try extensions!.replace(value: keyUsage) } let subjectPublicKeyInfo = SubjectPublicKeyInfo(algorithm: algorithm, subjectPublicKey: publicKey) @@ -156,6 +155,11 @@ public extension Certificate { notBefore: notBefore, notAfter: notAfter, extensions: extensions) } + public func extendedKeyUsage(keyPurposes: Set, isCritical: Bool) throws -> Builder { + + return try addExtension(value: ExtKeyUsage(keyPurposes: keyPurposes), isCritical: isCritical) + } + public func basicConstraints(ca: Bool, pathLength: Int? = nil) throws -> Builder { return try addExtension(value: BasicConstraints(ca: ca, pathLenConstraint: pathLength)) diff --git a/Sources/ShieldX509/CertificationRequestBuilder.swift b/Sources/ShieldX509/CertificationRequestBuilder.swift index 663e3edbc..d77dc5f20 100644 --- a/Sources/ShieldX509/CertificationRequestBuilder.swift +++ b/Sources/ShieldX509/CertificationRequestBuilder.swift @@ -9,6 +9,7 @@ // import Foundation +import PotentASN1 public extension CertificationRequest { @@ -70,19 +71,33 @@ public extension CertificationRequest { algorithm: AlgorithmIdentifier, usage keyUsage: KeyUsage? = nil) throws -> Builder { - var attributes = self.attributes - if let keyUsage = keyUsage { - attributes = attributes ?? CRAttributes() - var extensions = try attributes!.first(Extensions.self) ?? Extensions() - extensions.remove(KeyUsage.self) - try extensions.append(value: keyUsage) + var builder = self - attributes!.remove(Extensions.self) - attributes!.append(singleValued: extensions) + if let keyUsage = keyUsage { + builder = try builder.extension(Extension(value: keyUsage)) } let subjectPKInfo = SubjectPublicKeyInfo(algorithm: algorithm, subjectPublicKey: publicKey) + return Builder(subject: builder.subject, subjectPKInfo: subjectPKInfo, attributes: builder.attributes) + } + + public func extendedKeyUsage(keyPurposes: Set, isCritical: Bool) throws -> Builder { + + let extKeyUsage = ExtKeyUsage(keyPurposes: keyPurposes) + + return try `extension`(Extension(value: extKeyUsage, critical: isCritical)) + } + + public func `extension`(_ extension: Extension) throws -> Builder { + + var attributes = self.attributes ?? CRAttributes() + var extensions = try attributes.first(Extensions.self) ?? Extensions() + + extensions.replace(`extension`) + + attributes.replace(singleValued: extensions) + return Builder(subject: subject, subjectPKInfo: subjectPKInfo, attributes: attributes) } diff --git a/Sources/ShieldX509/ExtensionExtKeyUsage.swift b/Sources/ShieldX509/ExtensionExtKeyUsage.swift new file mode 100644 index 000000000..869c48688 --- /dev/null +++ b/Sources/ShieldX509/ExtensionExtKeyUsage.swift @@ -0,0 +1,59 @@ +// +// ExtensionKeyUsage.swift +// Shield +// +// Copyright © 2019 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +import PotentASN1 +import ShieldOID + + +public struct ExtKeyUsage: Equatable, Hashable, Codable, ExtensionValue { + + public static var extensionID = iso_itu.ds.certificateExtension.extKeyUsage.oid + public static var asn1Schema = Schemas.extKeyUsageExtension + + public let keyPurposes: Set + + public init(keyPurposes: Set) { + self.keyPurposes = keyPurposes + } +} + + + +// MARK: Schemas + +public extension Schemas { + + static let extKeyUsageExtension: Schema = .sequenceOf(.objectIdentifier()) + +} + + +// MARK: KeyUsage Conformances + +extension ExtKeyUsage { + + public init(from decoder: Decoder) throws { + var container = try decoder.unkeyedContainer() + var keyPurposes = Set() + for _ in 0 ..< (container.count ?? 0) { + keyPurposes.insert(try container.decode(OID.self)) + } + self.keyPurposes = keyPurposes + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.unkeyedContainer() + for keyPurpose in keyPurposes { + try container.encode(keyPurpose) + } + } + +} diff --git a/Tests/CertificateBuilderTests.swift b/Tests/CertificateBuilderTests.swift index 2cf1e66b3..53b93e7c2 100644 --- a/Tests/CertificateBuilderTests.swift +++ b/Tests/CertificateBuilderTests.swift @@ -10,6 +10,7 @@ import BigInt import PotentASN1 +import ShieldOID @testable import Shield import XCTest @@ -122,6 +123,8 @@ class CertificateBuilderTests: XCTestCase { .subject(name: subject, uniqueID: subjectID) .subjectAlternativeNames(names: .dnsName("github.com/outfoxx/Shield")) .publicKey(keyPair: keyPair) + .extendedKeyUsage(keyPurposes: [iso.org.dod.internet.security.mechanisms.pkix.kp.serverAuth.oid], + isCritical: false) .issuer(name: issuer, uniqueID: issuerID) .valid(for: 86400 * 365) .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256) @@ -223,7 +226,9 @@ class CertificateBuilderTests: XCTestCase { try CertificationRequest.Builder() .subject(name: NameBuilder().add("Shield Subject", forTypeName: "CN").name) .alternativeNames(names: altNames) - .publicKey(keyPair: keyPair) + .publicKey(keyPair: keyPair, usage: [.dataEncipherment]) + .extendedKeyUsage(keyPurposes: [iso.org.dod.internet.security.mechanisms.pkix.kp.serverAuth.oid], + isCritical: false) .build(signingKey: keyPair.privateKey, digestAlgorithm: .sha256) .encoded()