Skip to content

Adds support for including signing time in signedAttrs for CMS signing. #176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 21, 2024

Conversation

R4N
Copy link
Contributor

@R4N R4N commented May 29, 2024

Motivation:

Many CMS signing validations require the inclusion of signing time in signedAttrs (for example Apple Wallet's Pass CMS signing validation)

Modifications:

  • Adds optional includeSigningTime flag to CMS.sign method (defaults to false)
  • When includeSigningTime flag is true: includes signing-time (UTCTime ASN1Type), content-type, and message-digest signedAttrs and signs those instead of content bytes (as specified in RFC 5652 section 5.4)
  • Adds contentType ASN1ObjectIdentifier
  • Adds CMSSigningError (noTimeZone, invalidSigningDateComponent) for when includeSigningTime is true and unable to get UTC time zone or date components
  • Adds tests for valid and invalid CMS signature when including signedAttrs

Result:

Providing the includeSigningTime = true flag to CMS.sign will cause signing-time, content-type, and message-digest signedAttrs to be included in the CMS signature and the signature bytes will represent signing those signedAttrs. Using CMS.sign without the flag (defaulting to false) should leave the previous CMS.sign behavior unchanged.

Resolves: #147

Submitted on behalf of Zetetic, LLC

Copy link
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! I've left a few notes in the diff.

@@ -15,6 +15,11 @@ import Foundation
import SwiftASN1
import Crypto

public enum CMSSigningError: Error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's avoid using an enum error here. We've got a pattern of struct-based errors elsewhere in the project (see CertificateError), can we reproduce it or use it here?

return try sign(
var signature: Certificate.Signature
var completeSignedAttrs: [CMSAttribute]? = nil
if includeSigningTime {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we factor this brach out to a separate internal method? It makes it harder to audit if we don't.

@@ -400,3 +481,27 @@ extension Certificate.Signature {
)
}
}

extension CMSSigningError: LocalizedError {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not conform to LocalizedError, it's not worth it.

@@ -23,15 +28,82 @@ public enum CMS {
signatureAlgorithm: Certificate.SignatureAlgorithm,
additionalIntermediateCertificates: [Certificate] = [],
certificate: Certificate,
privateKey: Certificate.PrivateKey
privateKey: Certificate.PrivateKey,
includeSigningTime: Bool = false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is quite the right spelling: we should have the signing time provided as a Date?, and if we want to omit it then it can be nil.

signedAttrs.append(contentTypeAttribute)

// add message-digest sha256 of provided content bytes
try SHA256.hash(data: bytes).withUnsafeBytes { bufferPointer in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we factor this out to a small helper that is an init on ASN1OctetString? It's nice to reduce the scope of the unsafe pointer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not need any unsafe Swift here. The returned digest conforms to Sequence<UInt8>. Just convert the returned bytes to an ArraySlice.

}
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = timeZone
let nowComponents = calendar.dateComponents(desiredComponents, from: Date())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid this. We have a Date.utcTime transformation that already exists.

signedAttrs.append(contentTypeAttribute)

// add message-digest sha256 of provided content bytes
try SHA256.hash(data: bytes).withUnsafeBytes { bufferPointer in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not need any unsafe Swift here. The returned digest conforms to Sequence<UInt8>. Just convert the returned bytes to an ArraySlice.

signedAttrs.append(contentTypeAttribute)

// add message-digest sha256 of provided content bytes
try SHA256.hash(data: bytes).withUnsafeBytes { bufferPointer in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, here. No need for unsafe Swift.

@R4N
Copy link
Contributor Author

R4N commented May 31, 2024

Thanks for the review/feedback. Definitely good suggestions. Using Date.utcDate to construct the UTCTime from date components allows me to remove the previously defined CMSSigningErrors as both of the defined errors were related to DateComponents construction.

@R4N R4N requested review from dnadoba and Lukasa May 31, 2024 18:28
) throws -> [UInt8] {
if let signingTime = signingTime {
return try signWithSigningTime(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we prefer to use explicit self in all places.

signatureBytes: ASN1OctetString(signature),
signatureAlgorithm: signatureAlgorithm,
additionalIntermediateCertificates: additionalIntermediateCertificates,
certificate: certificate
)

return try serializeSignedData(signedData)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit here, prefer self.

certificate: certificate,
signedAttrs: signedAttrs
)
return try serializeSignedData(signedData)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note on self here.

@R4N R4N requested a review from Lukasa June 4, 2024 13:58
@R4N
Copy link
Contributor Author

R4N commented Jun 4, 2024

Added in the explicit self's for the internal methods calls within CMSOperations.

@Lukasa
Copy link
Contributor

Lukasa commented Jun 7, 2024

@swift-server-bot test this please

@Lukasa Lukasa added the 🔨 semver/patch No public API change. label Jun 7, 2024
@Lukasa
Copy link
Contributor

Lukasa commented Jun 7, 2024

These failures aren't your fault, our allocation baselines have regressed. I'll update them.

@Lukasa
Copy link
Contributor

Lukasa commented Jun 7, 2024

Feel free to rebase on to the new main branch.

@R4N
Copy link
Contributor Author

R4N commented Jun 7, 2024

Apologies, I thought it was going to give me the option to "Update with rebase" in the GH UI on my fork, but it just merged instead. What's the best way to proceed at this point?

@R4N
Copy link
Contributor Author

R4N commented Jun 7, 2024

I believe that should fix it up with the rebase now.

@Lukasa
Copy link
Contributor

Lukasa commented Jun 7, 2024

@swift-server-bot test this please

R4N added 3 commits June 20, 2024 10:41
Motivation:

Many CMS signing validations require the inclusion of signing time in signedAttrs (for example Apple Wallet's Pass CMS signing validation)

Modifications:

* Adds optional includeSigningTime flag to CMS.sign method (defaults to false)
* When includeSigningTime flag is true: includes signing-time (UTCTime ASN1Type), content-type, and message-digest signedAttrs and signs those instead of content bytes (as specified in RFC 5652 section 5.4)
* Adds contentType ASN1ObjectIdentifier
* Adds CMSSigningError (noTimeZone, invalidSigningDateComponent) for when includeSigningTime is true and unable to get UTC time zone or date components
* Adds tests for valid and invalid CMS signature when including signedAttrs

Result:

Providing the includeSigningTime = true flag to CMS.sign will cause signing-time, content-type, and message-digest signedAttrs to be included in the CMS signature and the signature bytes will represent signing those signedAttrs. Using CMS.sign without the flag (defaulting to false) should leave the previous CMS.sign behavior unchanged.
Motivation:

Callers may want to provide their own signingTime (Date). Code improvements for inclusion of singing time in signedAttrs.

Modifications:

* Changes CMS.sign method to take Date? instead of Bool for signingTime so callers can provide their own signingTime Date
* Adds internal method CMS.signWithSigningTime to separate logic for CMS signing with signingTime in signedAttrs
* Uses Date.utcDate transformation to get DateComponents instead of manually constructing them within CMS.signWithSigningTime
* Removes CMSSigningError from CMSOperations as its no longer needed after removal of manual DateComponents construction
* Switches message digest SHA256 from using unsafe bytes to converting Sequence<UInt8> to ArraySlice directly

Result:

CMS.sign callers can now provide signingTime (Date). CMS.sign with signingTime signedAttrs code is improved.
…ons.

Motivation:

Conform to standards regaring usage of explicit self for internal methods calls.

Modifications:

* Adds explicit self to signWithSigningTime and serializeSignedData internal methods calls with CMSOperations

Result:

Internal CMSOperations method calls are now compliant to standards.
@R4N
Copy link
Contributor Author

R4N commented Jun 20, 2024

Just rebased off of main for the latest commit that came in. The last swift-server-bot check had a failure because of the CMS.sign API method signature changing. I believe this is a false positive because of the optional signingTime parameter addition. Let me know if anything else needs to be done on my end.

@Lukasa
Copy link
Contributor

Lukasa commented Jun 21, 2024

@swift-server-bot test this please

Copy link
Member

@dnadoba dnadoba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@Lukasa Lukasa merged commit 44f2a33 into apple:main Jun 21, 2024
5 of 6 checks passed
@Lukasa
Copy link
Contributor

Lukasa commented Jun 21, 2024

Merging over the API breakage.

@dnadoba
Copy link
Member

dnadoba commented Jun 21, 2024

Note that this is in theory an API breaking change as unapplied methods do not take default values into account:

// this fails to compile now but previously compiled successfully
let ref = CMS.sign(_:signatureAlgorithm:additionalIntermediateCertificates:certificate:privateKey:)

We can partially workaround this limitation by keeping the old signature and introducing an overload with the new parameter. The func with the old signature would just call into the new method.
However, this only partially solves the source break as

// error: Ambiguous use of 'CMS.sign'
let ref = CMS.sign

now becomes ambiguous and fails to compile as well.

I personally think this is fine though.
@Lukasa wdyt?

@Lukasa
Copy link
Contributor

Lukasa commented Jun 21, 2024

Yeah I don't mind at all. These are also CMS functions so they're not guaranteed to be API stable, so this isn't very egregious.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🔨 semver/patch No public API change.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

SignedAttributes in CMSSignerInfo
3 participants