-
Notifications
You must be signed in to change notification settings - Fork 73
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
Conversation
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
.
Tests/X509Tests/CMSTests.swift
Outdated
signedAttrs.append(contentTypeAttribute) | ||
|
||
// add message-digest sha256 of provided content bytes | ||
try SHA256.hash(data: bytes).withUnsafeBytes { bufferPointer in |
There was a problem hiding this comment.
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.
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 |
) throws -> [UInt8] { | ||
if let signingTime = signingTime { | ||
return try signWithSigningTime( |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
Added in the explicit |
@swift-server-bot test this please |
These failures aren't your fault, our allocation baselines have regressed. I'll update them. |
Feel free to rebase on to the new |
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? |
I believe that should fix it up with the rebase now. |
@swift-server-bot test this please |
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.
Just rebased off of main for the latest commit that came in. The last |
@swift-server-bot test this please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks!
Merging over the API breakage. |
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. // 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. |
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. |
Motivation:
Many CMS signing validations require the inclusion of signing time in signedAttrs (for example Apple Wallet's Pass CMS signing validation)
Modifications:
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