Skip to content

Commit de9cbd0

Browse files
committed
implement extractSigningEntity
1 parent 846c840 commit de9cbd0

File tree

2 files changed

+182
-7
lines changed

2 files changed

+182
-7
lines changed

Sources/PackageSigning/SignatureProvider.swift

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public enum SignatureProvider {
6464
let provider = format.provider
6565
return try await provider.extractSigningEntity(
6666
signature: signature,
67+
format: format,
6768
verifierConfiguration: verifierConfiguration
6869
)
6970
}
@@ -119,6 +120,9 @@ public enum SigningError: Error {
119120
case keyDoesNotSupportSignatureAlgorithm
120121
case signingIdentityNotSupported
121122
case unableToValidateSignature(String)
123+
case invalidSignature(String)
124+
case certificateInvalid(String)
125+
case certificateNotTrusted(SigningEntity)
122126
}
123127

124128
// MARK: - Signature formats and providers
@@ -177,6 +181,7 @@ protocol SignatureProviderProtocol {
177181

178182
func extractSigningEntity(
179183
signature: [UInt8],
184+
format: SignatureFormat,
180185
verifierConfiguration: VerifierConfiguration
181186
) async throws -> SigningEntity
182187
}
@@ -249,13 +254,6 @@ struct CMSSignatureProvider: SignatureProviderProtocol {
249254
}
250255
}
251256

252-
func extractSigningEntity(
253-
signature: [UInt8],
254-
verifierConfiguration: VerifierConfiguration
255-
) async throws -> SigningEntity {
256-
throw StringError("not implemented")
257-
}
258-
259257
func status(
260258
signature: [UInt8],
261259
content: [UInt8],
@@ -310,6 +308,66 @@ struct CMSSignatureProvider: SignatureProviderProtocol {
310308
throw SigningError.unableToValidateSignature("\(error)")
311309
}
312310
}
311+
312+
func extractSigningEntity(
313+
signature: [UInt8],
314+
format: SignatureFormat,
315+
verifierConfiguration: VerifierConfiguration
316+
) async throws -> SigningEntity {
317+
switch format {
318+
case .cms_1_0_0:
319+
do {
320+
let cmsSignature = try CMSSignature(derEncoded: signature)
321+
let signers = try cmsSignature.signers
322+
guard signers.count == 1, let signer = signers.first else {
323+
throw SigningError.invalidSignature("expected 1 signer but got \(signers.count)")
324+
}
325+
326+
let signingCertificate = signer.certificate
327+
328+
var trustRoots: [Certificate] = []
329+
if verifierConfiguration.includeDefaultTrustStore {
330+
trustRoots.append(contentsOf: CertificateStores.defaultTrustRoots)
331+
}
332+
trustRoots.append(contentsOf: try verifierConfiguration.trustedRoots.map { try Certificate($0) })
333+
334+
// Verifier uses these to build cert chain for validation
335+
// (see also notes in `status` method)
336+
var untrustedIntermediates: [Certificate] = []
337+
// WWDR intermediates are not required when signing with ADP certs,
338+
// (i.e., these intermediates may not be in the signature), hence
339+
// we include them here to ensure Verifier can build cert chain.
340+
untrustedIntermediates.append(contentsOf: Certificates.wwdrIntermediates)
341+
// For self-signed certificate, the signature should include intermediate(s).
342+
untrustedIntermediates.append(contentsOf: cmsSignature.certificates)
343+
344+
let policySet = self.buildPolicySet(configuration: verifierConfiguration, httpClient: self.httpClient)
345+
346+
var verifier = Verifier(rootCertificates: CertificateStore(trustRoots), policy: policySet)
347+
let result = await verifier.validate(
348+
leafCertificate: signingCertificate,
349+
intermediates: CertificateStore(untrustedIntermediates)
350+
)
351+
352+
switch result {
353+
case .validCertificate:
354+
return SigningEntity.from(certificate: signingCertificate)
355+
case .couldNotValidate(let validationFailures):
356+
if validationFailures.isEmpty {
357+
let signingEntity = SigningEntity.from(certificate: signingCertificate)
358+
throw SigningError.certificateNotTrusted(signingEntity)
359+
} else {
360+
throw SigningError
361+
.certificateInvalid("failures: \(validationFailures.map(\.policyFailureReason))")
362+
}
363+
}
364+
} catch let error as SigningError {
365+
throw error
366+
} catch {
367+
throw SigningError.invalidSignature("\(error)")
368+
}
369+
}
370+
}
313371
}
314372

315373
#if canImport(Security)

Tests/PackageSigningTests/SigningTests.swift

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,123 @@ final class SigningTests: XCTestCase {
818818
}
819819
#endif
820820

821+
func testCMS1_0_0ExtractSigningEntity() async throws {
822+
let keyAndCertChain = try tsc_await { self.ecTestKeyAndCertChain(callback: $0) }
823+
let signingIdentity = SwiftSigningIdentity(
824+
certificate: try Certificate(keyAndCertChain.leafCertificate),
825+
privateKey: try Certificate
826+
.PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey))
827+
)
828+
let content = Array("per aspera ad astra".utf8)
829+
830+
let signatureFormat = SignatureFormat.cms_1_0_0
831+
let signature = try SignatureProvider.sign(
832+
content: content,
833+
identity: signingIdentity,
834+
intermediateCertificates: keyAndCertChain.intermediateCertificates,
835+
format: signatureFormat,
836+
observabilityScope: ObservabilitySystem.NOOP
837+
)
838+
839+
let verifierConfiguration = VerifierConfiguration(
840+
trustedRoots: [keyAndCertChain.rootCertificate],
841+
includeDefaultTrustStore: false,
842+
certificateExpiration: .disabled,
843+
certificateRevocation: .disabled
844+
)
845+
846+
let signingEntity = try await SignatureProvider.extractSigningEntity(
847+
signature: signature,
848+
format: signatureFormat,
849+
verifierConfiguration: verifierConfiguration
850+
)
851+
852+
guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else {
853+
return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)")
854+
}
855+
XCTAssertEqual("Test (EC) leaf", name)
856+
XCTAssertEqual("Test (EC) org unit", organizationalUnit)
857+
XCTAssertEqual("Test (EC) org", organization)
858+
}
859+
860+
func testCMS1_0_0ExtractSigningEntityWithSelfSignedCertificate() async throws {
861+
let keyAndCertChain = try tsc_await { self.ecSelfSignedTestKeyAndCertChain(callback: $0) }
862+
let signingIdentity = SwiftSigningIdentity(
863+
certificate: try Certificate(keyAndCertChain.leafCertificate),
864+
privateKey: try Certificate
865+
.PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey))
866+
)
867+
let content = Array("per aspera ad astra".utf8)
868+
869+
let signatureFormat = SignatureFormat.cms_1_0_0
870+
let signature = try SignatureProvider.sign(
871+
content: content,
872+
identity: signingIdentity,
873+
intermediateCertificates: keyAndCertChain.intermediateCertificates,
874+
format: signatureFormat,
875+
observabilityScope: ObservabilitySystem.NOOP
876+
)
877+
878+
let verifierConfiguration = VerifierConfiguration(
879+
trustedRoots: [keyAndCertChain.rootCertificate],
880+
includeDefaultTrustStore: false,
881+
certificateExpiration: .disabled,
882+
certificateRevocation: .disabled
883+
)
884+
885+
let signingEntity = try await SignatureProvider.extractSigningEntity(
886+
signature: signature,
887+
format: signatureFormat,
888+
verifierConfiguration: verifierConfiguration
889+
)
890+
891+
guard case .unrecognized(let name, let organizationalUnit, let organization) = signingEntity else {
892+
return XCTFail("Expected SigningEntity.unrecognized but got \(signingEntity)")
893+
}
894+
XCTAssertEqual("Test (EC)", name)
895+
XCTAssertEqual("Test (EC) org unit", organizationalUnit)
896+
XCTAssertEqual("Test (EC) org", organization)
897+
}
898+
899+
func testCMS1_0_0ExtractSigningEntityWithUntrustedCertificate() async throws {
900+
let keyAndCertChain = try tsc_await { self.ecTestKeyAndCertChain(callback: $0) }
901+
let signingIdentity = SwiftSigningIdentity(
902+
certificate: try Certificate(keyAndCertChain.leafCertificate),
903+
privateKey: try Certificate
904+
.PrivateKey(P256.Signing.PrivateKey(derRepresentation: keyAndCertChain.privateKey))
905+
)
906+
let content = Array("per aspera ad astra".utf8)
907+
908+
let signatureFormat = SignatureFormat.cms_1_0_0
909+
let signature = try SignatureProvider.sign(
910+
content: content,
911+
identity: signingIdentity,
912+
intermediateCertificates: keyAndCertChain.intermediateCertificates,
913+
format: signatureFormat,
914+
observabilityScope: ObservabilitySystem.NOOP
915+
)
916+
917+
let verifierConfiguration = VerifierConfiguration(
918+
trustedRoots: [], // trust store is empty
919+
includeDefaultTrustStore: false,
920+
certificateExpiration: .disabled,
921+
certificateRevocation: .disabled
922+
)
923+
924+
do {
925+
_ = try await SignatureProvider.extractSigningEntity(
926+
signature: signature,
927+
format: signatureFormat,
928+
verifierConfiguration: verifierConfiguration
929+
)
930+
XCTFail("expected error")
931+
} catch {
932+
guard case SigningError.certificateNotTrusted = error else {
933+
return XCTFail("Expected error to be SigningError.certificateNotTrusted but got \(error)")
934+
}
935+
}
936+
}
937+
821938
private func ecTestKeyAndCertChain(callback: (Result<KeyAndCertChain, Error>) -> Void) {
822939
do {
823940
try fixture(name: "Signing", createGitRepo: false) { fixturePath in

0 commit comments

Comments
 (0)