diff --git a/Sources/CryptoExtras/RSA/RSA.swift b/Sources/CryptoExtras/RSA/RSA.swift index a6c13a89..57b673db 100644 --- a/Sources/CryptoExtras/RSA/RSA.swift +++ b/Sources/CryptoExtras/RSA/RSA.swift @@ -655,6 +655,7 @@ extension _RSA.Encryption { @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public struct Padding: Sendable { internal enum Backing { + case _weakAndInsecure_pkcs1v1_5 case pkcs1_oaep(Digest) } @@ -663,7 +664,18 @@ extension _RSA.Encryption { private init(_ backing: Backing) { self.backing = backing } - + + /// PKCS#1 v1.5 padding + /// + /// As defined by [RFC 8017 § 7.2](https://datatracker.ietf.org/doc/html/rfc8017#section-7.2). + /// + /// This padding exists only for legacy compatibility and is known to be + /// weak and insecure. This algorithm is vulnerable to chosen-ciphertext + /// attacks outlined in http://archiv.infsec.ethz.ch/education/fs08/secsem/bleichenbacher98.pdf. + /// + /// When you have a choice, you should always favor OAEP over this. + public static let _WEAK_AND_INSECURE_PKCS_V1_5 = Self(._weakAndInsecure_pkcs1v1_5) + /// PKCS#1 OAEP padding /// /// As defined by [RFC 8017 § 7.1](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1). @@ -711,6 +723,9 @@ extension _RSA.Encryption.PublicKey { /// 4096|PKCS-OAEP|470 bytes public func maximumEncryptSize(with padding: _RSA.Encryption.Padding) -> Int { switch padding.backing { + case ._weakAndInsecure_pkcs1v1_5: + // https://www.rfc-editor.org/rfc/rfc8017#section-7.2 + return (self.keySizeInBits / 8) - 11 case let .pkcs1_oaep(Digest): // https://datatracker.ietf.org/doc/html/rfc8017#section-7.1.1 return (self.keySizeInBits / 8) - (2 * Digest.hashBitLength / 8) - 2 diff --git a/Sources/CryptoExtras/RSA/RSA_boring.swift b/Sources/CryptoExtras/RSA/RSA_boring.swift index c0f3be1a..038bd744 100644 --- a/Sources/CryptoExtras/RSA/RSA_boring.swift +++ b/Sources/CryptoExtras/RSA/RSA_boring.swift @@ -418,6 +418,8 @@ extension BoringSSLRSAPublicKey { CCryptoBoringSSL_EVP_PKEY_encrypt_init(ctx) switch padding.backing { + case ._weakAndInsecure_pkcs1v1_5: + CCryptoBoringSSL_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) case let .pkcs1_oaep(digest): CCryptoBoringSSL_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) switch digest { @@ -883,6 +885,8 @@ extension BoringSSLRSAPrivateKey { CCryptoBoringSSL_EVP_PKEY_decrypt_init(ctx) switch padding.backing { + case ._weakAndInsecure_pkcs1v1_5: + CCryptoBoringSSL_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) case let .pkcs1_oaep(digest): CCryptoBoringSSL_EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) switch digest { diff --git a/Tests/CryptoExtrasTests/TestRSAEncryption.swift b/Tests/CryptoExtrasTests/TestRSAEncryption.swift index f3d2710a..6de25d02 100644 --- a/Tests/CryptoExtrasTests/TestRSAEncryption.swift +++ b/Tests/CryptoExtrasTests/TestRSAEncryption.swift @@ -169,6 +169,66 @@ final class TestRSAEncryption: XCTestCase { XCTAssertEqual(214, pubKey2048.maximumEncryptSize(with: .PKCS1_OAEP)) XCTAssertEqual(190, pubKey2048.maximumEncryptSize(with: .PKCS1_OAEP_SHA256)) } + + func testPKCS1() throws { + let pubKeyPEM = """ + -----BEGIN RSA PUBLIC KEY----- + MIIBCgKCAQEAv6ElnElHGQO1BC5wsU/S01tHK8GbCnDLkxkS1259kOU250pEjOJa + ceOGFnhYzE36KXmKTrGw3o1m5vgbQz88j7/tNjymAX990I3YdWTnGQYcypp8c4TD + wHIj5Q3OHYXAC0KUHRBSKBeS+QJybrMI6SAQbFpHh9C3Q9W3WTtSAVqs8VveS4Jc + j4a3K21MNeHgNfyxwn3KTrrNs/c0yOvWlwyfxYTdWLFVVp2hn6YVQUfo7twM4BCE + Xz/6gR03NpqjVqKeyBmmMtDIy82+BzG4vd3jm02zwNvahsBy9b2NCOjq3y2ud72b + Q4bYU9/r/ccApts5BIW8ASwmYSGSmE6MzwIDAQAB + -----END RSA PUBLIC KEY----- + """ + let privKeyPEM = """ + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/oSWcSUcZA7UE + LnCxT9LTW0crwZsKcMuTGRLXbn2Q5TbnSkSM4lpx44YWeFjMTfopeYpOsbDejWbm + +BtDPzyPv+02PKYBf33Qjdh1ZOcZBhzKmnxzhMPAciPlDc4dhcALQpQdEFIoF5L5 + AnJuswjpIBBsWkeH0LdD1bdZO1IBWqzxW95LglyPhrcrbUw14eA1/LHCfcpOus2z + 9zTI69aXDJ/FhN1YsVVWnaGfphVBR+ju3AzgEIRfP/qBHTc2mqNWop7IGaYy0MjL + zb4HMbi93eObTbPA29qGwHL1vY0I6OrfLa53vZtDhthT3+v9xwCm2zkEhbwBLCZh + IZKYTozPAgMBAAECggEAGncL9bCdFBRR/JjZUXOfvzbc9msPmXqIcvFEi+Ijj05I + rdqw6vAb45yzmQjX4qdmRDIX6tRZg/LtYjqjsT7bg1LTVOk9V/mei537ZgMgc3FH + qqd5Ro7wZfSdhnXIoIUnR6bTQ8xMPGM9FgzDdwxcz61w9zXkqRonJUQvxTAPHEaH + SiNhRP8LUjzB0Y2ZYVXMWbs0nPPrSE+xuzjcGRX3lvz7nNOM1N4EyWto1RVJIlry + 4EV8RFczo3BjPXZFbtval76AGPmurDVqBdHpDN6IBZdhz4ZX/0fq8NR2p8/6S5VZ + 4Ylcth1S3HErcnG2UqT8rl/P3m9idTv4EZOg6HziyQKBgQDsmxaQCFTJnDwxSQR+ + 4j9WsgDpSxvCdnUtMX9w77aw3EIdcHkhnX99jTvNkt3uwGAsVsx4x7ilj1eaZOfl + soMIX1WBBx11yN4GOw173VmzC0LtaBGTh/2ollxuNoEqYxkuKLNxWxTGW0uc5TVA + 0hK2c6cF4eZ5sH07aIU6HIIknQKBgQDPVkeyxhF6lvgobLxFQOChOyLVcb1EymnU + W1zF27HciA+0FuaiWTj69bKoR8d+ZIFtIzVvjo7MfoFRJvEZmDGy5+I8HhpSW6JQ + NLdaRI5RGYxbEGmmC48icknXioZJ8JOXhbVuMyT4uLaN5D1M47ZYaq75dPM83fqZ + BDc+izDdWwKBgQCJw5d0j9VGeni1va0nb/avNP/A1qG4LZ72jH6GtJysB+NbHtT4 + 1KqZ4PU0MlKUpGCbEIMHxEpn47l/RUec/765zkCL2ye1IBreh93HBFApJuJ2NwUc + 4K66TapN5eB5XLAZp0ssMns7L4csOG00a9zHbTmP/ENlEXUpdSc1ecnxJQKBgFsJ + n2G35mTVdREK7X/bBMbGmHzv/BMAbYd4tjuKQ4Z5l6uTgqE2W/aVe2S4X7f3mXy6 + QPRCvCC+Szm+x45dbTUI7CVJcnVHFvXwr7FK+NJTTXWOt1TZLngJhrLFeEFvCN83 + Lnq8qjcro7yZwvDH64DXFw0hdMv9C9O0Li2gIEyRAoGBAK+C7Stfm3vViV2YfByt + MI73t2rN+t3ffnKsXZtGzWW1kxv4cueiAdeM7QwE2AaN7yKzsSMfsSXe+/r69wUR + UPB8NcGLKWE/gJuIcitQx1HCbQZ3AplRK6xhjDVXG1A5SszQVx09hhq76JVBm0sJ + DDYta1f+sEfAS750XLJ7A1h0 + -----END PRIVATE KEY----- + """ + let pubKey = try _RSA.Encryption.PublicKey(pemRepresentation: pubKeyPEM) + let privKey = try _RSA.Encryption.PrivateKey(pemRepresentation: privKeyPEM) + let msgs = [ + // empty + "", + // short + "467A8AFB-9165-484A-8377-B66BCACD774A", + // example text + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla rutrum odio ut sem luctus, non finibus diam congue. Suspendisse nisl enim, placerat consectetur dolor non, mattis sollicitudin augue.", + // max length + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla rutrum odio ut sem luctus, non finibus diam congue. Suspendisse nisl enim, placerat consectetur dolor non, mattis sollicitudin augue. Pellentesque a est eget enim efficitur volutpat ", + ] + for msg in msgs { + let msgEnc = try pubKey.encrypt(msg.data(using: .utf8)!, padding: ._WEAK_AND_INSECURE_PKCS_V1_5) + let msgDec = String(data: try privKey.decrypt(msgEnc, padding: ._WEAK_AND_INSECURE_PKCS_V1_5), encoding: .utf8)! + XCTAssertEqual(msg, msgDec) + } + } } struct RSAEncryptionOAEPTestGroup: Codable {