Skip to content

Commit

Permalink
Support of the X509 header for any public key (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
SeaweedbrainCY authored Jul 28, 2021
1 parent 54c5aa4 commit d163550
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 15 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,21 @@ let reference = key.reference
let originalData = key.originalData
```

### Use X.509 certificate
SwiftyRSA supports X.509 certificate for public keys. SwiftyRSA can add the X.509 header to a headerless public key, or on the contrary strip it to get a key without a header.
#### Add an X.509 header to a public key
```swift
let publicKey = PublicKey(data: data)
let publicKeyData = try publicKey.data()
let publicKey_with_X509_header = try SwiftyRSA.prependX509KeyHeader(keyData: publicKeyData)
```
#### Strip the X.509 header from a public key
```swift
let publicKey_headerLess: Data = try SwiftyRSA.stripKeyHeader(keyData: publicKey_with_X509_header)
```

**Warning** : Storing (with SwiftyRSA's methods) or creating a ```PublicKey``` instance will automatically strip the header from the key. For more info, see *Under the hood* above.

Create public and private RSA keys
----------------------------------

Expand Down
31 changes: 31 additions & 0 deletions Source/SwiftyRSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SwiftyRSA
//
// Created by Loïs Di Qual on 7/2/15.
// Contributions by Stchepinsky Nathan on 24/06/2021
// Copyright (c) 2015 Scoop Technologies, Inc. All rights reserved.
//

Expand Down Expand Up @@ -285,6 +286,36 @@ public enum SwiftyRSA {
throw SwiftyRSAError.invalidAsn1Structure
}

/**
This method prepend the x509 header to the given PublicKey data.
If the key already contain a x509 header, the given data is returned as is.
It letterally does the opposite of the previous method :
From a given headerless key :
SEQUENCE
INTEGER (1024 or 2048 bit) -- modulo
INTEGER -- public exponent
the key is returned following the X509 header :
SEQUENCE
SEQUENCE
OBJECT IDENTIFIER 1.2.840.113549.1.1.1
NULL
BIT STRING
SEQUENCE
INTEGER (1024 or 2048 bit) -- modulo
INTEGER -- public exponent
*/

static func prependX509KeyHeader(keyData : Data) throws -> Data{
if try keyData.isAnHeaderlessKey(){
let x509certificate : Data = keyData.prependx509Header()
return x509certificate
} else if try keyData.hasX509Header() {
return keyData
} else { // invalideHeader
throw SwiftyRSAError.x509CertificateFailed
}
}

static func removeKey(tag: String) {

guard let tagData = tag.data(using: .utf8) else {
Expand Down
4 changes: 4 additions & 0 deletions Source/SwiftyRSAError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SwiftyRSA
//
// Created by Lois Di Qual on 5/15/17.
// Contributions by Stchepinsky Nathan on 24/06/2021
// Copyright © 2017 Scoop. All rights reserved.
//

Expand Down Expand Up @@ -32,6 +33,7 @@ public enum SwiftyRSAError: Error {
case derFileNotFound(name: String)
case notAPublicKey
case notAPrivateKey
case x509CertificateFailed

var localizedDescription: String {
switch self {
Expand Down Expand Up @@ -79,6 +81,8 @@ public enum SwiftyRSAError: Error {
return "Provided key is not a valid RSA public key"
case .notAPrivateKey:
return "Provided key is not a valid RSA pivate key"
case .x509CertificateFailed :
return "Couldn't prepend the provided key because it has an unexpected structure"
}
}
}
171 changes: 171 additions & 0 deletions Source/X509Certificate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//
// X509Certificate.swift
// SwiftyRSA
//
// Created by Stchepinsky Nathan on 24/06/2021.
// Copyright © 2021 Scoop. All rights reserved.
//

import Foundation


///
/// Encoding/Decoding lengths as octets
///
private extension NSInteger {
func encodedOctets() -> [CUnsignedChar] {
// Short form
if self < 128 {
return [CUnsignedChar(self)];
}

// Long form
let i = Int(log2(Double(self)) / 8 + 1)
var len = self
var result: [CUnsignedChar] = [CUnsignedChar(i + 0x80)]

for _ in 0..<i {
result.insert(CUnsignedChar(len & 0xFF), at: 1)
len = len >> 8
}

return result
}

init?(octetBytes: [CUnsignedChar], startIdx: inout NSInteger) {
if octetBytes[startIdx] < 128 {
// Short form
self.init(octetBytes[startIdx])
startIdx += 1
} else {
// Long form
let octets = NSInteger(octetBytes[startIdx] as UInt8 - 128)

if octets > octetBytes.count - startIdx {
self.init(0)
return nil
}

var result = UInt64(0)

for j in 1...octets {
result = (result << 8)
result = result + UInt64(octetBytes[startIdx + j])
}

startIdx += 1 + octets
self.init(result)
}
}
}



public extension Data{
// This code source come from Heimdall project https://github.com/henrinormak/Heimdall published under MIT Licence

/// This method prepend the X509 header to a given public key
func prependx509Header() -> Data {
let result = NSMutableData()

let encodingLength: Int = (self.count + 1).encodedOctets().count
let OID: [CUnsignedChar] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00]

var builder: [CUnsignedChar] = []

// ASN.1 SEQUENCE
builder.append(0x30)

// Overall size, made of OID + bitstring encoding + actual key
let size = OID.count + 2 + encodingLength + self.count
let encodedSize = size.encodedOctets()
builder.append(contentsOf: encodedSize)
result.append(builder, length: builder.count)
result.append(OID, length: OID.count)
builder.removeAll(keepingCapacity: false)

builder.append(0x03)
builder.append(contentsOf: (self.count + 1).encodedOctets())
builder.append(0x00)
result.append(builder, length: builder.count)

// Actual key bytes
result.append(self)

return result as Data
}

func hasX509Header() throws -> Bool{
let node: Asn1Parser.Node
do {
node = try Asn1Parser.parse(data: self)
} catch {
throw SwiftyRSAError.asn1ParsingFailed
}


// Ensure the raw data is an ASN1 sequence
guard case .sequence(let nodes) = node else {
return false
}

// Must contain 2 elements, a sequence and a bit string
if nodes.count != 2 {
return false
}

// Ensure the first node is an ASN1 sequence
guard case .sequence(let firstNode) = nodes[0] else {
return false
}

// Must contain 2 elements, an object id and NULL
if firstNode.count != 2 {
return false
}

guard case .objectIdentifier(_) = firstNode[0] else {
return false
}

guard case .null = firstNode[1] else {
return false
}

// The 2sd child has to be a bit string containing a sequence of 2 int


let last = nodes[1]
if case .bitString(let secondChildSequence) = last {
return try secondChildSequence.isAnHeaderlessKey()
} else {
return false
}
}

func isAnHeaderlessKey() throws -> Bool{
let node: Asn1Parser.Node
do {
node = try Asn1Parser.parse(data: self)
} catch {
throw SwiftyRSAError.asn1ParsingFailed
}

// Ensure the raw data is an ASN1 sequence
guard case .sequence(let nodes) = node else {
return false
}

// Detect whether the sequence only has integers, in which case it's a headerless key
let onlyHasIntegers = nodes.filter { node -> Bool in
if case .integer = node {
return false
}
return true
}.isEmpty

// Headerless key
return onlyHasIntegers
}
}
20 changes: 20 additions & 0 deletions SwiftyRSA.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@
2C9144BB1528A03A53582ADA158D7F71 /* NSData+SHA.m in Sources */ = {isa = PBXBuildFile; fileRef = D77B6899031A54A9F698D3430A599421 /* NSData+SHA.m */; };
47C6D29039697B1E71B856F957188617 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CEBB75D7D70F1A095D22C1D7BFD8773 /* Security.framework */; };
61C3EEC5859298BF282B0BA572B8FCAC /* NSData+SHA.h in Headers */ = {isa = PBXBuildFile; fileRef = 5AAC360BC9A1D1773D59C8E91186044E /* NSData+SHA.h */; settings = {ATTRIBUTES = (Private, ); }; };
902623FE2687278D00489B07 /* X509Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902623FD2687278D00489B07 /* X509Certificate.swift */; };
902623FF2687278D00489B07 /* X509Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902623FD2687278D00489B07 /* X509Certificate.swift */; };
902624002687278D00489B07 /* X509Certificate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 902623FD2687278D00489B07 /* X509Certificate.swift */; };
90EBFC8526AB093B002B30E9 /* X509Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90EBFC8326AB08DF002B30E9 /* X509Tests.swift */; };
90EBFC8626AB093C002B30E9 /* X509Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90EBFC8326AB08DF002B30E9 /* X509Tests.swift */; };
90EBFC8926AC32B3002B30E9 /* swiftyrsa-public-base64-X509-format.txt in Resources */ = {isa = PBXBuildFile; fileRef = 90EBFC8726AC32B2002B30E9 /* swiftyrsa-public-base64-X509-format.txt */; };
90EBFC8A26AC32B3002B30E9 /* swiftyrsa-public-base64-X509-format.txt in Resources */ = {isa = PBXBuildFile; fileRef = 90EBFC8726AC32B2002B30E9 /* swiftyrsa-public-base64-X509-format.txt */; };
B0981AE770653C8FE25311592E2BCE5E /* SwiftyRSA.h in Headers */ = {isa = PBXBuildFile; fileRef = 1CB44E2E8BE22C702325440A0F96410A /* SwiftyRSA.h */; settings = {ATTRIBUTES = (Private, ); }; };
BF52D5FDEDA5DFCA62ECD03992601E82 /* SwiftyRSA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E84F551B29670A456D12366AA4C8E5 /* SwiftyRSA.swift */; };
C034317B1EC2D7E30019B7E8 /* Asn1Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C034317A1EC2D7E30019B7E8 /* Asn1Parser.swift */; };
Expand Down Expand Up @@ -113,6 +120,9 @@
5AAC360BC9A1D1773D59C8E91186044E /* NSData+SHA.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+SHA.h"; sourceTree = "<group>"; };
6E2D7A1B9C4D58D0887FB023C0E15594 /* swiftyrsa-private-headerless.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "swiftyrsa-private-headerless.pem"; sourceTree = "<group>"; };
7DF0A3222330CDFCF12918ED08519EB9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
902623FD2687278D00489B07 /* X509Certificate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = X509Certificate.swift; sourceTree = "<group>"; };
90EBFC8326AB08DF002B30E9 /* X509Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = X509Tests.swift; sourceTree = "<group>"; };
90EBFC8726AC32B2002B30E9 /* swiftyrsa-public-base64-X509-format.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "swiftyrsa-public-base64-X509-format.txt"; sourceTree = "<group>"; };
986B7C763D1B85C628621E89E7071B06 /* swiftyrsa-public.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "swiftyrsa-public.der"; sourceTree = "<group>"; };
9E21744A2073DB8AC7BAEB324D36D4DB /* TestUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestUtils.swift; sourceTree = "<group>"; };
AC84097FE3BCDEAEE6370ADD41E9AE68 /* swiftyrsa-public-headerless.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "swiftyrsa-public-headerless.pem"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -221,6 +231,7 @@
D77B6899031A54A9F698D3430A599421 /* NSData+SHA.m */,
C0E83E041ECD7DC400E9D63C /* PrivateKey.swift */,
C0E83DFC1ECD7DA300E9D63C /* PublicKey.swift */,
902623FD2687278D00489B07 /* X509Certificate.swift */,
C096A00B1D90754E00E285C2 /* Signature.swift */,
38E84F551B29670A456D12366AA4C8E5 /* SwiftyRSA.swift */,
C06A33DA1E6E815A000EE6DC /* SwiftyRSA+ObjC.swift */,
Expand All @@ -244,6 +255,7 @@
children = (
D240C55056247A7B270C92143EF30532 /* keys */,
C096A00F1D9083D000E285C2 /* KeyTests.swift */,
90EBFC8326AB08DF002B30E9 /* X509Tests.swift */,
C096A0131D9086F300E285C2 /* MessageTests.swift */,
C096A0151D9092C700E285C2 /* EncryptDecryptTests.swift */,
C096A0171D90969A00E285C2 /* SignatureTests.swift */,
Expand All @@ -261,6 +273,7 @@
C065ED6E1DBF314500674763 /* swiftyrsa-private.der */,
C096A0111D9085C100E285C2 /* swiftyrsa-public-base64.txt */,
AD7F28E3936301CA24DF13E22196C73B /* multiple-keys-testcase.pem */,
90EBFC8726AC32B2002B30E9 /* swiftyrsa-public-base64-X509-format.txt */,
47807FD4AF81104758D4EA9B778F5186 /* multiple-keys-testcase.sh */,
6E2D7A1B9C4D58D0887FB023C0E15594 /* swiftyrsa-private-headerless.pem */,
EF193719CB8D4DDE8D347FF699311D1D /* swiftyrsa-private.pem */,
Expand Down Expand Up @@ -495,6 +508,7 @@
C0579268215465A60057F401 /* swiftyrsa-private-headerless.pem in Resources */,
C057926C215465A60057F401 /* swiftyrsa-public.pem in Resources */,
C057926D215465A60057F401 /* swiftyrsa-private-header-octetstring.pem in Resources */,
90EBFC8A26AC32B3002B30E9 /* swiftyrsa-public-base64-X509-format.txt in Resources */,
C0579269215465A60057F401 /* swiftyrsa-private.pem in Resources */,
C057926B215465A60057F401 /* swiftyrsa-public.der in Resources */,
C0579265215465A60057F401 /* swiftyrsa-public-base64.txt in Resources */,
Expand All @@ -513,6 +527,7 @@
C076F5531DADEC1D006AF5DB /* swiftyrsa-public-headerless.pem in Resources */,
C04D33811DC54D5700D65B05 /* swiftyrsa-public-base64-newlines.txt in Resources */,
C084A72A1EC3034B003F79ED /* swiftyrsa-private-header-octetstring.pem in Resources */,
90EBFC8926AC32B3002B30E9 /* swiftyrsa-public-base64-X509-format.txt in Resources */,
C076F5541DADEC1D006AF5DB /* swiftyrsa-public.der in Resources */,
C076F5551DADEC1D006AF5DB /* swiftyrsa-public.pem in Resources */,
C065ED6F1DBF314500674763 /* swiftyrsa-private.der in Resources */,
Expand Down Expand Up @@ -544,6 +559,7 @@
buildActionMask = 2147483647;
files = (
C0E83E0D1ECD7F4200E9D63C /* EncryptedMessage.swift in Sources */,
902623FE2687278D00489B07 /* X509Certificate.swift in Sources */,
C096A00C1D90754E00E285C2 /* Signature.swift in Sources */,
2C9144BB1528A03A53582ADA158D7F71 /* NSData+SHA.m in Sources */,
C096A0081D9071CA00E285C2 /* Message.swift in Sources */,
Expand All @@ -563,6 +579,7 @@
buildActionMask = 2147483647;
files = (
C057922321545CB00057F401 /* ClearMessage.swift in Sources */,
902624002687278D00489B07 /* X509Certificate.swift in Sources */,
C057922421545CB00057F401 /* Asn1Parser.swift in Sources */,
C057922521545CB00057F401 /* EncryptedMessage.swift in Sources */,
C057922621545CB00057F401 /* Key.swift in Sources */,
Expand All @@ -582,6 +599,7 @@
buildActionMask = 2147483647;
files = (
C0579242215465350057F401 /* EncryptedMessage.swift in Sources */,
902623FF2687278D00489B07 /* X509Certificate.swift in Sources */,
C057924A215465350057F401 /* SwiftyRSA+ObjC.swift in Sources */,
C0579243215465350057F401 /* Key.swift in Sources */,
C0579249215465350057F401 /* SwiftyRSA.swift in Sources */,
Expand All @@ -603,6 +621,7 @@
C05792612154659B0057F401 /* TestUtils.swift in Sources */,
C05792602154659B0057F401 /* SignatureTests.swift in Sources */,
C057925E2154659B0057F401 /* MessageTests.swift in Sources */,
90EBFC8626AB093C002B30E9 /* X509Tests.swift in Sources */,
C057925D2154659B0057F401 /* KeyTests.swift in Sources */,
C05792622154659B0057F401 /* ObjCTests.m in Sources */,
C057925F2154659B0057F401 /* EncryptDecryptTests.swift in Sources */,
Expand All @@ -616,6 +635,7 @@
C076F5481DADEBC2006AF5DB /* KeyTests.swift in Sources */,
C076F5491DADEBC2006AF5DB /* MessageTests.swift in Sources */,
C076F54A1DADEBC2006AF5DB /* EncryptDecryptTests.swift in Sources */,
90EBFC8526AB093B002B30E9 /* X509Tests.swift in Sources */,
C076F54B1DADEBC2006AF5DB /* SignatureTests.swift in Sources */,
C076F54C1DADEBC2006AF5DB /* TestUtils.swift in Sources */,
C076F54D1DADEBC2006AF5DB /* ObjCTests.m in Sources */,
Expand Down
Loading

0 comments on commit d163550

Please sign in to comment.