Swift Certificates and Swift ASN.1 1.0.0 released

I’m exited to announce the first major release of swift-certificates and swift-asn1. Together, these libraries provide developers a faster and safer implementation of X.509 certificates, a critical technology that powers the security of TLS.

Since its introduction in March, the API was refined and the performance has been improved to be ready to replace the BoringSSL X.509 layer in swift-nio-ssl. The public API is now considered stable and ready for widespread adoption.

To find out more about these Swift packages please refer to the Introduction blog post or the online documentation for swift-certificates and swift-asn1.

We’re really excited for what Swift Certificates can do for the Swift community, and we look forward to seeing it unlock a wide range of new use-cases and technologies.

24 Likes

Is there any example code of using this to create a NIOSSL TLSConfiguration for testing an HTTP server? The self signed certificate swift-certificates Documentation – Swift Package Index in the documentation can't be used as a leaf certificate. Do I need to create another certificate for this?.

I have some dummy code that I used for creating a TLSConfiguration from swift-certificates but it isn't great and probably not the best cert to use. Only used for testing purposes, so I am hiding it in a spoiler here :smiley:

Summary
let now = Date()
let issuerKey = P256.Signing.PrivateKey()
let issuerName = try DistinguishedName {
    CommonName("Issuer")
}
let leafKey = P256.Signing.PrivateKey()
let leafName = try DistinguishedName {
    CommonName("Leaf")
}
let leaf = try Certificate(
    version: .v3,
    serialNumber: .init(),
    publicKey: .init(leafKey.publicKey),
    notValidBefore: now - 5000,
    notValidAfter: now + 5000,
    issuer: issuerName,
    subject: leafName,
    signatureAlgorithm: .ecdsaWithSHA256,
    extensions: try Certificate.Extensions {
        Critical(
            BasicConstraints.notCertificateAuthority
        )
    },
    issuerPrivateKey: .init(issuerKey)
)
let certificateChain = try NIOSSLCertificate.fromPEMBytes(Array(leaf.serializeAsPEM().pemString.utf8))
var tlsConfiguration = try TLSConfiguration.makeServerConfiguration(
    certificateChain: certificateChain.map { .certificate($0) },
    privateKey: .privateKey(.init(bytes: Array(leafKey.pemRepresentation.utf8), format: NIOSSLSerializationFormats.pem))
)

I only need something like this for a test server anyway, so keep going! How can I get the local system to trust cert enough to make a request to it without SSL errors?

2 Likes

i currently use mkcert for this, and it works well for browser testing (guide here). unfortunately, i have not figured out how to get it working inside a docker container. i’d be really interested in knowing if anyone’s figured out how to test a client-server duo in a docker container that uses TLS.

swift-nio-ssl is currently Foundation-free, it looks like the new implementation links Foundation, which from my perspective is a regression. is factoring this out into an overlay on the roadmap?

We have discussed this before and I don’t think removing Foundation as a dependency from swift-certificates is possible due to the dependency on swift-crypto. Tough @dnadoba or @lukasa can expand on this more.

I have been able to test a dummy HTTPServer using Certificates, NIOSSL and NIOHTTP1 in docker with curl already. However, I just disabled verification by passing -k to curl

that’s disappointing, i really hope swift-nio-ssl will not become infected with a Foundation dependency, because then we will be stuck between equally bad options of linking the framework, or not updating swift-nio-ssl which will gradually become more insecure over time.

is there a way to disable certificate verification in swift-nio-http2? i am currently using NIO on both the client and server sides :slight_smile:

swift-nio-http2 doesn't do any certificate verification (and doesn't depend on swift-nio-ssl) but swift-nio-ssl does it. Certificate verification in swift-nio-ssl can be disabled by setting TLSConfiguration.certificateVerification to .none. However, you can also add your self-signed certificate (or issuer in Franz's example) to TLSConfiguration.additionalTrustRoots without disabling verification which I would recommend.


Is this still a problem with apple/swift-foundation? I think we currently only depend on Data and Date (very small subset, e.g. no formatting or parsing).

If you need no formatting or parsing, wouldn't you be able to use [UInt8] or even some Collection<UInt8> (that Data itself conforms to) instead of concrete Data, and Double (or whatever TimeInterval is a typealias for) instead of Date, representing it as seconds since epoch?

2 Likes

Posix also has various types for dates that should be widely available (including Windows).

swift-crypto accepts some DataProtocol (in some cases some RandomAccessCollection<UInt8>) as input but Data as output. As long as the output type does not change we can’t stop using Data. swift-crypto just reexports CryptoKit on Darwin and wants to be API compatible where possible with CryptoKit on other platforms so this isn't something swift-certificates or swift-crypto can change.

For all other purposes swift-certificates already uses Array<UInt8> or ArraySlice<UInt8>.

Could you wrap those in

#if canImport(Foundation)
x() -> Data
#endif

But also provide [UInt8] variants? That should be easy to shim?

Thinking about it, that canImport is always true on any platform. Hm.

i’m aware of the dependency graph, i was talking more loosely in the sense that HTTP/2 requires TLS to work. but yeah, adding additional trust roots to the client is probably the way to go here.

i’m not sure if that repository is the current Foundation that ships with the toolchain, my understanding is that the new Foundation is still in active development and it will be some time until it supplants the Foundation that gets linked by SPM.

but at any rate, yes it is still a problem. the new Foundation is an improvement in that the heavy ICU/localization stuff has been moved to FoundationInternationalization, but the remainder of FoundationEssentials still duplicates far too much of what i already have in my server binary. (e.g. a second JSON parser, a second UUID, a second attributed string, et cetera et cetera.)

i am not hating on Foundation because i recognize it is very valuable for client applications, especially if you have many different swift binaries loaded into memory at a given time. but its architecture just isn’t a great fit for server applications where you typically have several processes launched from one binary with different arguments.

i think this reflects a deeper issue in that we haven’t really defined well what the actual goals are of the swift-certificates project.

i am sure that swift-certificates is interesting to a lot of people for a lot of different reasons. but from a server-side perspective, you would rarely, if ever, be interested in using swift-certificates on a Darwin platform. the reason is simple economics: macOS hosts are about twice as expensive per CPU hour (65¢ for 6 cores vs 0.51¢ for 0.1 cores), and at least on AWS, you cannot purchase partial instances, you must purchase dedicated instances which greatly raises the barrier to entry.

that’s not to say that swift-certificates wouldn’t still have value as a client-optimized networking library. but that’s a matter of defining goals for the project.

FWIW HTTP/2 doesn't actually require TLS. As Franz already said in the linked post it also works with prior knowledge and gRPC uses it as gRPC only runs over HTTP/2 and never over HTTP/1.x.


This is correct but it is a goal of the project:

In the future, we will explore how to sunset the existing swift-corelibs-foundation and migrate to using the new version of Foundation created by this project.
GitHub - apple/swift-foundation: The Foundation project


What's the size overhead we are talking about here? Will you still need a second JSON parser (and the other types you have listed) once apple/swift-foundation replaces swift-corelibs-foundation?

1 Like

i did a quick test with the server binary that currently lives on swiftinit (unidoc 0.3.1) compared to a modified version that has an import Foundation and a trivial usage of Date compiled with the same settings, and got the following results, by observing the processes in top:

RES SHR
No Foundation 22812 19328
With Foundation 33804 29184
% change +48.2% + 51.0%

i also want to note that the swiftinit server is an unusually forgiving case study, its server binary is relatively large (already about 67 MB currently without Foundation :frowning: ) which means the percentage increase due to loading Foundation appears smaller than it normally would.

aside

i hope these numbers do not scare people away from using swift on the server, to me it is proof that you can actually cut swift memory usage by over a third if you avoid Foundation. i hope people do not take away the wrong lesson from reading this post.

yes. in particular, swift-unidoc has a completely parallel Codable system that is optimized for efficient BSON-to-JSON interop.

3 Likes

Not sure it’d be a huge difference - but that was with the old Foundation - not FoundationEssentials ?

I couldn't get this to work with a single certificate. If a certificate is flagged as a certificate authority NIOSSL refuses to load it. I ended up with three one for the issuer, then one for the server and one for the client both of which pointed back to the issuer.

how do you go about this with a server that supports both HTTP/1.1 and HTTP/2?

the issue i am currently grappling with is that it is really difficult to test a client-sever combination that uses TLS, especially when you throw docker into the mix because none of the entities involved trust each others’ certificates, but it is also really difficult to test a client-server combination that uses a completely different channel pipeline than the “real” server.

because the easiest way to test a client-server setup is with HTTP/2-only and no TLS, but this is not really great if you’re trying to develop with “security in mind” (as opposed to “security as an afterthought”).