Skip to content

Add support for encrypted PEM keys #318

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

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/_CryptoExtras/AES/AES_CBC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ extension AES {
}

if !noPadding {
try plaintext.trimPadding()
try plaintext.trimCBCPadding()
}
return plaintext
}
Expand Down Expand Up @@ -193,7 +193,7 @@ extension AES._CBC {
}

extension Data {
fileprivate mutating func trimPadding() throws {
mutating func trimCBCPadding() throws {
guard let paddingBytes = self.last else {
// Degenerate case, empty string. This is forbidden:
// we must always pad.
Expand Down
4 changes: 4 additions & 0 deletions Sources/_CryptoExtras/RSA/RSA.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ extension _RSA.Signing {
throw CryptoKitError.incorrectParameterSize
}
}

public init(encryptedPEMRepresentation: String, encryptionPassword: String) throws {
self.backing = try BackingPrivateKey(encryptedPEMRepresentation: encryptedPEMRepresentation, encryptionPassword: encryptionPassword)
}

/// Construct an RSA private key from a DER representation.
///
Expand Down
2 changes: 1 addition & 1 deletion Sources/_CryptoExtras/RSA/RSA_boring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal struct BoringSSLRSAPublicKey: Sendable {
}

func getKeyPrimitives() throws -> (n: Data, e: Data) {
try self.backing.getKeyPrimitives()
self.backing.getKeyPrimitives()
}
}

Expand Down
29 changes: 28 additions & 1 deletion Sources/_CryptoExtras/RSA/RSA_security.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,24 @@ internal struct SecurityRSAPrivateKey: @unchecked Sendable {
default:
throw _CryptoRSAError.invalidPEMDocument
}

}

init(encryptedPEMRepresentation: String, encryptionPassword: String) throws {
let document = try EncryptedPEMDocument(pemEncoded: encryptedPEMRepresentation)
let pem = try document.decrypt(withPassword: encryptionPassword)

switch pem.discriminator {
case _RSA.PKCS1KeyType:
// This is what is expected by Security.framework
self = try .init(derRepresentation: pem.derBytes)
case _RSA.PKCS8KeyType:
guard let pkcs8Bytes = pem.derBytes.pkcs8RSAKeyBytes else {
throw _CryptoRSAError.invalidPEMDocument
}
self = try .init(derRepresentation: pkcs8Bytes)
default:
throw _CryptoRSAError.invalidPEMDocument
}
}

init<Bytes: DataProtocol>(derRepresentation: Bytes) throws {
Expand Down Expand Up @@ -457,6 +474,16 @@ extension Int {
}
}

extension [UInt8] {
var pkcs8RSAKeyBytes: [UInt8]? {
let bytes = Data(self).pkcs8RSAKeyBytes
guard let bytes else {
return nil
}
return [UInt8](bytes)
}
}

extension UInt {
// Bytes needed to store a given integer in 7 bit bytes.
fileprivate var neededBytes: Int {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2021 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import SwiftASN1

extension EncryptedPEMDocument {
struct EncryptionScheme: DERParseable {
static var defaultIdentifier: SwiftASN1.ASN1Identifier { .sequence }

let encryptionAlgorithm: ASN1ObjectIdentifier
let encryptionAlgorithmParameters: ASN1OctetString

init(encryptionAlgorithm: ASN1ObjectIdentifier, encryptionAlgorithmParameters: ASN1OctetString) {
self.encryptionAlgorithm = encryptionAlgorithm
self.encryptionAlgorithmParameters = encryptionAlgorithmParameters
}

init(derEncoded node: ASN1Node) throws {
self = try DER.sequence(node, identifier: .sequence) { nodes in
let encryptionAlgorithm = try ASN1ObjectIdentifier(derEncoded: &nodes)
let encryptionAlgorithmParameters = try ASN1OctetString(derEncoded: &nodes)

return .init(encryptionAlgorithm: encryptionAlgorithm, encryptionAlgorithmParameters: encryptionAlgorithmParameters)
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2021 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import SwiftASN1

extension EncryptedPEMDocument {
struct KeyDerivationFunction: DERParseable {
static var defaultIdentifier: ASN1Identifier { .sequence }

let algorithm: ASN1ObjectIdentifier
let parameters: ASN1Any

init(algorithm: ASN1ObjectIdentifier, parameters: ASN1Any) {
self.algorithm = algorithm
self.parameters = parameters
}

init(derEncoded node: ASN1Node) throws {
self = try DER.sequence(node, identifier: .sequence) { nodes in
let algorithm = try ASN1ObjectIdentifier(derEncoded: &nodes)
let parameters = try ASN1Any(derEncoded: &nodes)

return .init(algorithm: algorithm, parameters: parameters)
}
}
}
}

extension EncryptedPEMDocument.KeyDerivationFunction {
// PBKDF2-params ::= SEQUENCE {
// salt CHOICE {
// specified OCTET STRING,
// otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
// },
// iterationCount INTEGER (1..MAX),
// keyLength INTEGER (1..MAX) OPTIONAL,
// prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
// }
struct PBKDF2Parameters: DERParseable {
static var defaultIdentifier: ASN1Identifier { .sequence }

let salt: ASN1OctetString
let iterationCount: any ASN1IntegerRepresentable
let hashFunction: HashFunction

init(salt: ASN1OctetString, iterationCount: any ASN1IntegerRepresentable, hashFunction: HashFunction) {
self.salt = salt
self.iterationCount = iterationCount
self.hashFunction = hashFunction
}

init(derEncoded node: ASN1Node) throws {
self = try DER.sequence(node, identifier: .sequence) { nodes in
let salt = try ASN1OctetString(derEncoded: &nodes)
let iterationCount = try Int(derEncoded: &nodes)
let hashFunction = try HashFunction(derEncoded: &nodes)

return .init(salt: salt, iterationCount: iterationCount, hashFunction: hashFunction)
}
}
}
}

extension EncryptedPEMDocument.KeyDerivationFunction.PBKDF2Parameters {
struct HashFunction: DERParseable {
static var defaultIdentifier: ASN1Identifier { .sequence }

let objectIdentifer: ASN1ObjectIdentifier
let null: ASN1Null

init(objectIdentifer: ASN1ObjectIdentifier, null: ASN1Null) {
self.objectIdentifer = objectIdentifer
self.null = null
}

init(derEncoded node: ASN1Node) throws {
self = try DER.sequence(node, identifier: .sequence) { nodes in
let objectIdentifer = try ASN1ObjectIdentifier(derEncoded: &nodes)
let null = try ASN1Null(derEncoded: &nodes)

return .init(objectIdentifer: objectIdentifer, null: null)
}
}
}
}

extension KDF.Insecure.PBKDF2.HashFunction {
static func from(objectIdentifier: ASN1ObjectIdentifier) -> Self? {
switch objectIdentifier.oidComponents {
case [2, 16, 840, 1, 101, 3, 4, 2, 1],
[1, 2, 840, 113549, 2, 9]: // hmacWithSHA256
.sha256
case [2, 16, 840, 1, 101, 3, 4, 2, 2]:
.sha384
case [2, 16, 840, 1, 101, 3, 4, 2, 3]:
.sha512
default: nil
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCrypto open source project
//
// Copyright (c) 2021 Apple Inc. and the SwiftCrypto project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCrypto project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import SwiftASN1

extension EncryptedPEMDocument {
struct PBES2Parameters: DERParseable {
static var defaultIdentifier: ASN1Identifier { .sequence }

let keyDerivationFunction: KeyDerivationFunction
let encryptionScheme: EncryptionScheme

init(keyDerivationFunction: KeyDerivationFunction, encryptionScheme: EncryptionScheme) {
self.keyDerivationFunction = keyDerivationFunction
self.encryptionScheme = encryptionScheme
}

init(derEncoded node: ASN1Node) throws {
self = try DER.sequence(node, identifier: .sequence) { nodes in
let keyDerivationFunction = try KeyDerivationFunction(derEncoded: &nodes)
let encryptionScheme = try EncryptionScheme(derEncoded: &nodes)

return .init(keyDerivationFunction: keyDerivationFunction, encryptionScheme: encryptionScheme)
}
}
}
}
Loading