├── src ├── data │ ├── alg-ed448.der │ ├── alg-ed25519.der │ ├── alg-ecdsa-p256.der │ ├── alg-ecdsa-p384.der │ ├── alg-ecdsa-p521.der │ ├── alg-ml-dsa-44.der │ ├── alg-ml-dsa-65.der │ ├── alg-ml-dsa-87.der │ ├── alg-ecdsa-p256k1.der │ ├── alg-ecdsa-sha256.der │ ├── alg-ecdsa-sha384.der │ ├── alg-ecdsa-sha512.der │ ├── alg-rsa-encryption.der │ ├── alg-rsa-pkcs1-sha256.der │ ├── alg-rsa-pkcs1-sha384.der │ ├── alg-rsa-pkcs1-sha512.der │ ├── alg-rsa-pss-sha256.der │ ├── alg-rsa-pss-sha384.der │ ├── alg-rsa-pss-sha512.der │ └── README.md ├── alg_id.rs ├── pem.rs ├── base64.rs └── lib.rs ├── .gitignore ├── rustfmt.toml ├── fuzz ├── corpus │ └── pem │ │ └── zen.pem ├── .gitignore ├── fuzz_targets │ ├── private_key.rs │ └── pem.rs ├── Cargo.toml └── Cargo.lock ├── deny.toml ├── tests ├── data │ ├── gunk.pem │ ├── nistp256curve.pem │ ├── nistp256key.pem │ ├── nistp256key.pkcs8.pem │ ├── ech.pem │ ├── spki.pem │ ├── rsa1024.pkcs1.pem │ ├── rsa1024.pkcs8.pem │ ├── crl.pem │ ├── csr.pem │ ├── whitespace-prefix.crt │ ├── certificate.pem │ ├── certificate.chain.pem │ ├── rsa-key-no-trailing-newline.pem │ ├── mixed-line-endings.crt │ └── zen.pem ├── keys │ ├── eddsakey.der │ ├── ed448.pkcs8.der │ ├── edd25519_v2.der │ ├── nistp256key.der │ ├── nistp384key.der │ ├── nistp521key.der │ ├── ecdsap256k1key.der │ ├── nistp256key.pkcs8.der │ ├── nistp384key.pkcs8.der │ ├── nistp521key.pkcs8.der │ ├── rsa2048key.pkcs1.der │ ├── rsa2048key.pkcs8.der │ ├── rsa4096key.pkcs8.der │ └── ecdsap256k1key.pkcs8.der ├── server_name.rs ├── key_type.rs ├── pem.rs └── dns_name.rs ├── LICENSE-MIT ├── Cargo.toml ├── README.md ├── Cargo.lock ├── .github └── workflows │ └── ci.yml └── LICENSE-APACHE /src/data/alg-ed448.der: -------------------------------------------------------------------------------- 1 | +eq -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea 3 | -------------------------------------------------------------------------------- /src/data/alg-ed25519.der: -------------------------------------------------------------------------------- 1 | +ep -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | style_edition = "2024" 2 | -------------------------------------------------------------------------------- /fuzz/corpus/pem/zen.pem: -------------------------------------------------------------------------------- 1 | ../../../tests/data/zen.pem -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | version = 2 3 | allow = ["Apache-2.0", "MIT", "Unicode-3.0"] 4 | -------------------------------------------------------------------------------- /tests/data/gunk.pem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/data/gunk.pem -------------------------------------------------------------------------------- /tests/keys/eddsakey.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/eddsakey.der -------------------------------------------------------------------------------- /src/data/alg-ecdsa-p256.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ecdsa-p256.der -------------------------------------------------------------------------------- /src/data/alg-ecdsa-p384.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ecdsa-p384.der -------------------------------------------------------------------------------- /src/data/alg-ecdsa-p521.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ecdsa-p521.der -------------------------------------------------------------------------------- /src/data/alg-ml-dsa-44.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ml-dsa-44.der -------------------------------------------------------------------------------- /src/data/alg-ml-dsa-65.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ml-dsa-65.der -------------------------------------------------------------------------------- /src/data/alg-ml-dsa-87.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ml-dsa-87.der -------------------------------------------------------------------------------- /tests/keys/ed448.pkcs8.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/ed448.pkcs8.der -------------------------------------------------------------------------------- /tests/keys/edd25519_v2.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/edd25519_v2.der -------------------------------------------------------------------------------- /tests/keys/nistp256key.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/nistp256key.der -------------------------------------------------------------------------------- /tests/keys/nistp384key.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/nistp384key.der -------------------------------------------------------------------------------- /tests/keys/nistp521key.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/nistp521key.der -------------------------------------------------------------------------------- /src/data/alg-ecdsa-p256k1.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ecdsa-p256k1.der -------------------------------------------------------------------------------- /src/data/alg-ecdsa-sha256.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ecdsa-sha256.der -------------------------------------------------------------------------------- /src/data/alg-ecdsa-sha384.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ecdsa-sha384.der -------------------------------------------------------------------------------- /src/data/alg-ecdsa-sha512.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-ecdsa-sha512.der -------------------------------------------------------------------------------- /tests/keys/ecdsap256k1key.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/ecdsap256k1key.der -------------------------------------------------------------------------------- /src/data/alg-rsa-encryption.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-rsa-encryption.der -------------------------------------------------------------------------------- /src/data/alg-rsa-pkcs1-sha256.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-rsa-pkcs1-sha256.der -------------------------------------------------------------------------------- /src/data/alg-rsa-pkcs1-sha384.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-rsa-pkcs1-sha384.der -------------------------------------------------------------------------------- /src/data/alg-rsa-pkcs1-sha512.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-rsa-pkcs1-sha512.der -------------------------------------------------------------------------------- /src/data/alg-rsa-pss-sha256.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-rsa-pss-sha256.der -------------------------------------------------------------------------------- /src/data/alg-rsa-pss-sha384.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-rsa-pss-sha384.der -------------------------------------------------------------------------------- /src/data/alg-rsa-pss-sha512.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/src/data/alg-rsa-pss-sha512.der -------------------------------------------------------------------------------- /tests/data/nistp256curve.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BggqhkjOPQMBBw== 3 | -----END EC PARAMETERS----- 4 | -------------------------------------------------------------------------------- /tests/keys/nistp256key.pkcs8.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/nistp256key.pkcs8.der -------------------------------------------------------------------------------- /tests/keys/nistp384key.pkcs8.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/nistp384key.pkcs8.der -------------------------------------------------------------------------------- /tests/keys/nistp521key.pkcs8.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/nistp521key.pkcs8.der -------------------------------------------------------------------------------- /tests/keys/rsa2048key.pkcs1.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/rsa2048key.pkcs1.der -------------------------------------------------------------------------------- /tests/keys/rsa2048key.pkcs8.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/rsa2048key.pkcs8.der -------------------------------------------------------------------------------- /tests/keys/rsa4096key.pkcs8.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/rsa4096key.pkcs8.der -------------------------------------------------------------------------------- /tests/keys/ecdsap256k1key.pkcs8.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rustls/pki-types/HEAD/tests/keys/ecdsap256k1key.pkcs8.der -------------------------------------------------------------------------------- /fuzz/fuzz_targets/private_key.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | let _ = rustls_pki_types::PrivateKeyDer::try_from(data); 7 | }); 8 | -------------------------------------------------------------------------------- /tests/data/nistp256key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIMaA7bFrjDDBSik057bIKo7UQXJZNwLK9AjYZQ7yIWFloAoGCCqGSM49 3 | AwEHoUQDQgAExu0Z/w8nQJZAXeOXOnZun9HiZscY9H/KwYcXpeZHu+f9P9mOUEkH 4 | 5Z0av+JKtzhFspjngNLVgWcjlA1L5AJLdA== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /tests/data/nistp256key.pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxoDtsWuMMMFKKTTn 3 | tsgqjtRBclk3Asr0CNhlDvIhYWWhRANCAATG7Rn/DydAlkBd45c6dm6f0eJmxxj0 4 | f8rBhxel5ke75/0/2Y5QSQflnRq/4kq3OEWymOeA0tWBZyOUDUvkAkt0 5 | -----END PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /tests/data/ech.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VuBCIEICjd4yGRdsoP9gU7YT7My8DHx1Tjme8GYDXrOMCi8v1V 3 | -----END PRIVATE KEY----- 4 | -----BEGIN ECHCONFIG----- 5 | AD7+DQA65wAgACA8wVN2BtscOl3vQheUzHeIkVmKIiydUhDCliA4iyQRCwAEAAEA 6 | AQALZXhhbXBsZS5jb20AAA== 7 | -----END ECHCONFIG----- 8 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustls-pki-types-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4" 12 | 13 | [dependencies.rustls-pki-types] 14 | path = ".." 15 | features = ["std"] 16 | 17 | # Prevent this from interfering with workspaces 18 | [workspace] 19 | members = ["."] 20 | 21 | [profile.release] 22 | debug = 1 23 | 24 | [[bin]] 25 | name = "private_key" 26 | path = "fuzz_targets/private_key.rs" 27 | test = false 28 | doc = false 29 | 30 | [[bin]] 31 | name = "pem" 32 | path = "fuzz_targets/pem.rs" 33 | test = false 34 | doc = false 35 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/pem.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use std::io::Cursor; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | use rustls_pki_types::pem::PemObject; 8 | use rustls_pki_types::{CertificateDer, PrivateKeyDer}; 9 | 10 | fuzz_target!(|data: &[u8]| { 11 | // cover the code paths that use std::io 12 | for x in CertificateDer::pem_reader_iter(&mut Cursor::new(data)) { 13 | match x { 14 | Ok(_item) => (), 15 | Err(_err) => break, 16 | } 17 | } 18 | 19 | // cover the code paths that use slices 20 | for x in PrivateKeyDer::pem_slice_iter(data) { 21 | match x { 22 | Ok(_item) => (), 23 | Err(_err) => break, 24 | } 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /src/data/README.md: -------------------------------------------------------------------------------- 1 | These files contain the binary DER encoding of the *values* of some 2 | ASN.1 [`AlgorithmIdentifier`]s, without the outer `SEQUENCE` tag or the outer 3 | length component. 4 | 5 | These files were encoded with the help of [der-ascii]. They can be decoded 6 | using: 7 | 8 | ```sh 9 | go install github.com/google/der-ascii/cmd/der2ascii@latest 10 | der2ascii -i -o .ascii 11 | ``` 12 | 13 | New or modified der-ascii files can be encoded using: 14 | 15 | ```sh 16 | go install github.com/google/der-ascii/cmd/ascii2der@latest 17 | ascii2der i .ascii -o 18 | ``` 19 | 20 | [`AlgorithmIdentifier`]: https://tools.ietf.org/html/rfc5280#section-4.1.1.2] 21 | [der-ascii]: https://github.com/google/der-ascii 22 | -------------------------------------------------------------------------------- /tests/data/spki.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqIh8FTj9DIgI8DAoCBh+ 3 | 6UXOfaWkvNaGZx2GwXl4WDAa/ZSE5/8ofg/6V59bmk9yry57UR4F+blscBvE4g3U 4 | dTvWJOBRD900l21vwpDLKzZguyGOCmKwJu3vCnAQKzBRXW5sDgvO67GeU6kpaic9 5 | LYPYnYaoxCRTYTZu0wy72rW5G0Fe8Gg/duJmUH7vqGIZupTTVzIBMbFVPBMJqprT 6 | MStDhaUL0JiAz0ZgTeNLRIBZWV9mY4PG3rZtbV0BZGR1ipAq9xfgqJcURCcKl/ZT 7 | UMtzvgk8s5hYkIJX0ZL3qsfdM4BMgIFhHq/GisQKbbu9kWldBrxQylOwa6r0m3Jv 8 | KJX2ViDSORndaCz2sppmVx5HDHnj+Bw381yawphnpumP3BJK4iof//uYKvfdc4RC 9 | y2EXL8PYPsT5DMB0jaBt92ytR5sLhn8Sl9Hk0buN4IjrYPISrdhS45xQXUqxcp9O 10 | 9hcU+rSaQyZ45cj+VlWhKq8MDvGvaAONBFSEh01mnUwoJObsAZNVFVtuOkwAli0F 11 | kGouMycQY1BGscpdC516Nya361Hk/ICyby2Y0BJrrVGaSM6poXH9yEjglzAdtSDb 12 | Cvhn/zlAI5ltm4Nv2qTgYBDns5JRGVhBym6RbbZ1C/KfCgn0hOxiw3N7AN4d0K5n 13 | LI6p7U9RnNVbWgbqsuoxBtkCAwEAAQ== 14 | -----END PUBLIC KEY----- 15 | -------------------------------------------------------------------------------- /tests/data/rsa1024.pkcs1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQC1Dt8tFmGS76ciuNXvk/QRrV8wCcArWxvl7Ku0aSQXgcFBAav6 3 | P5RD8b+dC9DihSu/r+6OOfjsAZ6oKCq3OTUfmoUhLpoBomxPczJgLyyLD+nQkp5q 4 | B1Q3WB6ACL/HJRRjJEIn7lc5u1FVBGbiCAHKMiaP4BDSym8oqimKC6uiaQIDAQAB 5 | AoGAGKmY7sxQqDIqwwkIYyT1Jv9FqwZ4/a7gYvZVATMdLnKHP3KZ2XGVoZepcRvt 6 | 7R0Us3ykcw0kgglKcj9eaizJtnSuoDPPwt53mDypPN2sU3hZgyk2tPgr49DB3MIp 7 | fjoqw4RL/p60ksgGXbDEqBuXqOtH5i61khWlMj+BWL9VDq0CQQDaELWPQGjgs+7X 8 | /QyWMJwOF4FXE4jecH/CcPVDB9K1ukllyC1HqTNe44Sp2bIDuSXXWb8yEixrEWBE 9 | ci2CSSjXAkEA1I4W9IzwEmAeLtL6VBip9ks52O0JKu373/Xv1F2GYdhnQaFw7IC6 10 | 1lSzcYMKGTmDuM8Cj26caldyv19Q0SPmvwJAdRHjZzS9GWWAJJTF3Rvbq/USix0B 11 | renXrRvXkFTy2n1YSjxdkstTuO2Mm2M0HquXlTWpX8hB8HkzpYtmwztjoQJAECKl 12 | LXVReCOhxu4vIJkqtc6qGoSL8J1WRH8X8KgU3nKeDAZkWx++jyyo3pIS/y01iZ71 13 | U8wSxaPTyyFCMk4mYwJBALjg7g8yDy1Lg9GFfOZvAVzPjqD28jZh/VJsDz9IhYoG 14 | z89iHWHkllOisbOm+SeynVC8CoFXmJPc26U65GcjI18= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/data/rsa1024.pkcs8.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALUO3y0WYZLvpyK4 3 | 1e+T9BGtXzAJwCtbG+Xsq7RpJBeBwUEBq/o/lEPxv50L0OKFK7+v7o45+OwBnqgo 4 | Krc5NR+ahSEumgGibE9zMmAvLIsP6dCSnmoHVDdYHoAIv8clFGMkQifuVzm7UVUE 5 | ZuIIAcoyJo/gENLKbyiqKYoLq6JpAgMBAAECgYAYqZjuzFCoMirDCQhjJPUm/0Wr 6 | Bnj9ruBi9lUBMx0ucoc/cpnZcZWhl6lxG+3tHRSzfKRzDSSCCUpyP15qLMm2dK6g 7 | M8/C3neYPKk83axTeFmDKTa0+Cvj0MHcwil+OirDhEv+nrSSyAZdsMSoG5eo60fm 8 | LrWSFaUyP4FYv1UOrQJBANoQtY9AaOCz7tf9DJYwnA4XgVcTiN5wf8Jw9UMH0rW6 9 | SWXILUepM17jhKnZsgO5JddZvzISLGsRYERyLYJJKNcCQQDUjhb0jPASYB4u0vpU 10 | GKn2SznY7Qkq7fvf9e/UXYZh2GdBoXDsgLrWVLNxgwoZOYO4zwKPbpxqV3K/X1DR 11 | I+a/AkB1EeNnNL0ZZYAklMXdG9ur9RKLHQGt6detG9eQVPLafVhKPF2Sy1O47Yyb 12 | YzQeq5eVNalfyEHweTOli2bDO2OhAkAQIqUtdVF4I6HG7i8gmSq1zqoahIvwnVZE 13 | fxfwqBTecp4MBmRbH76PLKjekhL/LTWJnvVTzBLFo9PLIUIyTiZjAkEAuODuDzIP 14 | LUuD0YV85m8BXM+OoPbyNmH9UmwPP0iFigbPz2IdYeSWU6Kxs6b5J7KdULwKgVeY 15 | k9zbpTrkZyMjXw== 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /tests/data/crl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN X509 CRL----- 2 | MIICiTBzAgEBMA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNVBAMMD3Bvbnl0b3duIFJT 3 | QSBDQRcNMjMwNjI3MDgyODEyWhcNMjMwNzI3MDgyODEyWjAVMBMCAgHIFw0yMzA2 4 | MjcwODI3NTlaoA4wDDAKBgNVHRQEAwIBAjANBgkqhkiG9w0BAQsFAAOCAgEAP6EX 5 | 9+hxjx/AqdBpynZXjGkEqigBcLcJ2PADOXngdQI1jC0WuYnZymUimemeULtt8X+1 6 | ai2KxAuF1m4NEKZsrGKvO+/9s/X1xbGroyHSAMKtZafFopFpoB2aNbYlx7yIyLtD 7 | BBIZIF50g20U+3izqpHutTD10itdk9TLsSceJHpwTkNJtaWMkOfBV28nKzEzVutV 8 | f6WzRpURGzui6nQy7aIqImeanpoBoz323psMfC32U0uMBCZltyHNqsX58/2Uhucx 9 | 0IPnitNuhv4scCPf/jeRfGIWDrTf1/25LDzRxyg1S4z9aa+3GM4O3dqy4igZEhgT 10 | q3pjlJ2hUL5E0oqbZDIQD1SN8UUUv5N2AjwZcxVBNnYeGyuO7YpTBYiu62o73iL2 11 | CjgElfaMq/9hEr9GR9kJozh7VTxtQPbnr4DiucQvhv8o/A1z+zkC0gj8iCLFtDbO 12 | 8bvDowcdle9LKkrLaBe6sO+fSH/I9Wj8vrEJKsuwaEraIdEaq2VrIMUPEWN0/MH9 13 | vTwHyadGSMK4CWtrn9fCAgSLw6NX74D7Cx1IaS8vstMjpeUqOS0dk5ThiW47HceB 14 | DTko7rV5N+RGH2nW1ynLoZKCJQqqZcLilFMyKPui3jifJnQlMFi54jGVgg/D6UQn 15 | 7dA7wb2ux/1hSiaarp+mi7ncVOyByz6/WQP8mfc= 16 | -----END X509 CRL----- 17 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Dirkjan Ochtman 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /tests/data/csr.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC+zCCAeMCAQAwfDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRQwEgYD 3 | VQQHDAtTYW4gQW50b25pbzEdMBsGA1UECgwURXhhbXBsZSBPcmdhbml6YXRpb24x 4 | EjAQBgNVBAsMCU1hcmtldGluZzEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0G 5 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwJw0BcbuqZyiABlmSYTi1tcr8DB0D 6 | NcTtzsYe7tlyIKd3mEs+u6Pi3rEQGvOw5eo6CmWII2qmVOqJ2f6gjl2lZJ5DUE6B 7 | I+NNE73zfFMrttUtI8X4ChnE4rrGqqUsSvYz1YVU0KiJ/00YMjEY5XlJYYa9FgfZ 8 | sUrhj4aCFdXS6CU9jueRr+udEBElDcgTS9+pB+LFhVfUMTdxnJ3BcT4ZDDqODH3/ 9 | 5RAgq03dhRpkkaVIg2uVKTBDoM3hs8T1zIxLM7hItaZzMv4uHdfI8y+BdHrePT33 10 | BoTlocvTEZEqqXEdw2kUd4PDgyUTjFE3b9OeLk0Ju5GRvuCW3UcS5gFvAgMBAAGg 11 | OjA4BgkqhkiG9w0BCQ4xKzApMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29tgg9mb28u 12 | ZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBACWsgxPw13QUpoCJOqvp8B1A 13 | EfsxRJITSROmukV3ZQycPT76Y3GVrM9sGjO8p13J/CVw2KcWc9xmgHF0MdvPNhnW 14 | OB6Y07hVpNnJVHb1KglOkNkTy6sVDtnZHg2klqGSyzIbwZ9R3JG8HtRdkceIrm3D 15 | gdiZyLcf1VDCCUGaskEi2CsggCQQJNyGi+8BSQ8MPKm/m0KrSchGQ157eWCCjopz 16 | f5GQe2UGOg5T7g8+S4GdECMwkMlTGUwlAM6LuOG/NZqP528PCAYQv0eOYdSwALQT 17 | GwTyU4AZ9y1uBFuaFxABew9GbDEtNY/XHTF8308edUwGBk6jfD+UuTeEwRZGs9E= 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /tests/data/whitespace-prefix.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDaTCCAlGgAwIBAgIJAOq/zL+84IswMA0GCSqGSIb3DQEBCwUAMFoxCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIDAJOQzEMMAoGA1UEBwwDUlRQMQ8wDQYDVQQKDAZOZXRB 4 | cHAxDTALBgNVBAsMBEVTSVMxEDAOBgNVBAMMB1NTRk1DQ0EwHhcNMTcxMTAxMjEw 5 | OTQyWhcNMjcxMDMwMjEwOTQyWjBaMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTkMx 6 | DDAKBgNVBAcMA1JUUDEPMA0GA1UECgwGTmV0QXBwMQ0wCwYDVQQLDARFU0lTMRAw 7 | DgYDVQQDDAdTU0ZNQ0NBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 8 | iaD9Ee0Yrdka0I+9GTJBIW/Fp5JU6kyjaxfOldW/R9lEubegXQFhDD2Xi1HZ+fTM 9 | f224glB9xLJXAHhipRK01C2MgC4kSH75WL1iAiYeOBloExqmK6OCX+sdyO7RXm/H 10 | Ra9tN2INWdvyO2pnmxsSnq56mCMsUZLtrRKp89FWgcxLg5r8QxH7xwfh5k54rxjE 11 | 144TD9yrIiQOgRSIRHUrVJ9l/F/gnwzP8wcNABeXwN71Mzl7mliPA703kONQIAyU 12 | 0E0tLpmy/U8dZdMmTBZGB7jI9f95Hl1RunfwhR371a6z38kgkvwrLzl4qflfsPjw 13 | K9n4omNk9rCH9H9tWkxxjwIDAQABozIwMDAdBgNVHQ4EFgQU/bFyCCnqdDFKlQBJ 14 | ExtV6wcMYkEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAOQMs 15 | Pz2iBD1+3RcSOsahB36WAwPCjgPiXXXpU+Zri11+m6I0Lq+OWtf+YgaQ8ylLmCQd 16 | 0p1wHlYA4qo896SycrhTQfy9GlS/aQqN192k3oBGoJcMIUnGUBGuEvyZ2aDUfkzy 17 | JUqBe+0KaT7pkvvbRL7VUz34I7ouq9fQIRZ26vUDLTY3KM1n/DXBj3e30GHGMV3K 18 | NN2twuLXPNjnryfgpliHU1rwV7r1WvrCVn4StjimP2bO5HGqD/SbiYUL2M9LOuLK 19 | 6mqY4OHumYXq3k7CHrvt0FepsN0L14LYEt1LvpPDFWP3SdN4z4KqT9AGqBaJnhhl 20 | Qiq8GWnAChspdBLxCg== 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustls-pki-types" 3 | version = "1.13.2" 4 | edition = "2021" 5 | rust-version = "1.60" 6 | license = "MIT OR Apache-2.0" 7 | description = "Shared types for the rustls PKI ecosystem" 8 | documentation = "https://docs.rs/rustls-pki-types" 9 | homepage = "https://github.com/rustls/pki-types" 10 | repository = "https://github.com/rustls/pki-types" 11 | categories = ["network-programming", "data-structures", "cryptography"] 12 | include = ["Cargo.toml", "LICENSE-APACHE", "LICENSE-MIT", "src/**/*.rs", "README.md", "src/data/*.der"] 13 | 14 | [features] 15 | default = ["alloc"] 16 | alloc = ["dep:zeroize"] 17 | std = ["alloc"] 18 | web = ["web-time"] 19 | 20 | [dependencies] 21 | zeroize = { version = "1", optional = true } 22 | 23 | [target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dev-dependencies] 24 | crabgrind = "=0.1.9" # compatible with valgrind package on GHA ubuntu-latest 25 | 26 | [target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] 27 | web-time = { version = "1", optional = true } 28 | 29 | [lints.rust] 30 | unexpected_cfgs = { level = "warn", check-cfg = ["cfg(rustls_pki_types_docsrs)"] } 31 | 32 | [package.metadata.docs.rs] 33 | all-features = true 34 | rustdoc-args = ["--cfg", "rustls_pki_types_docsrs"] 35 | 36 | [package.metadata.cargo_check_external_types] 37 | allowed_external_types = ["zeroize::Zeroize"] 38 | -------------------------------------------------------------------------------- /tests/server_name.rs: -------------------------------------------------------------------------------- 1 | use rustls_pki_types::ServerName; 2 | 3 | fn compile_time_assert_hash() {} 4 | fn compile_time_assert_send() {} 5 | fn compile_time_assert_sync() {} 6 | 7 | #[test] 8 | fn test_server_name_traits() { 9 | compile_time_assert_hash::(); 10 | compile_time_assert_send::(); 11 | compile_time_assert_sync::(); 12 | 13 | let a = ServerName::try_from(&b"example.com"[..]).unwrap(); 14 | 15 | // `Clone` 16 | #[allow(clippy::clone_on_copy)] 17 | let _ = a.clone(); 18 | // TODO: verify the clone is the same as `a`. 19 | 20 | // TODO: Don't require `alloc` for these. 21 | #[cfg(feature = "alloc")] 22 | { 23 | // `Debug`. 24 | assert_eq!(format!("{:?}", &a), "DnsName(\"example.com\")"); 25 | } 26 | } 27 | 28 | #[cfg(feature = "alloc")] 29 | #[test] 30 | fn test_alloc_server_name_traits() { 31 | let a_ref = ServerName::try_from(&b"example.com"[..]).unwrap(); 32 | let a = a_ref.to_owned(); 33 | 34 | // `Clone`, `Debug`, `PartialEq`. 35 | assert_eq!(&a, &a.clone()); 36 | 37 | // `Debug`. 38 | assert_eq!(format!("{:?}", &a), "DnsName(\"example.com\")"); 39 | 40 | // PartialEq is case-insensitive 41 | assert_eq!( 42 | a, 43 | ServerName::try_from(&b"Example.Com"[..]) 44 | .unwrap() 45 | .to_owned() 46 | ); 47 | 48 | // PartialEq isn't completely wrong. 49 | assert_ne!( 50 | a, 51 | ServerName::try_from(&b"fxample.com"[..]) 52 | .unwrap() 53 | .to_owned() 54 | ); 55 | assert_ne!( 56 | a, 57 | ServerName::try_from(&b"example.co"[..]).unwrap().to_owned() 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /tests/data/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEnzCCAoegAwIBAgIBezANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9wb255 3 | dG93biBSU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcNMjkwNjA2MTcxNTEyWjAsMSow 4 | KAYDVQQDDCFwb255dG93biBSU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwggGiMA0G 5 | CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCj/tOFeSW3WB+TtuLCR1L/84lZytFw 6 | zbpzOTGB1kPEKNbrMsv3lHXm5bHa8Bl3k113k7Hi7OAt/nkMm05s8LcUoovhaG5C 7 | G7tjzL+ld1nO74gNS3IQHCzxRdRwIgaDZHyICfBQBfB9/m+9z3yRtOKWJl6i/MT9 8 | HRN6yADW/8gHFlMzRkCKBjIKXehKsu8cbtB+5MukwtXI4rKf9aYXZQOEUn1kEwQJ 9 | ZIKBXR0eyloQiZervUE7meRCTBvzXT9VoSEX49/mempp4hnfdHlRNzre4/tphBf1 10 | fRUdpVXZ3DvmzoHdXRVzxx3X5LvDpf7Eb3ViGkXDFwkSfHEhkRnAl4lIzTH/1F25 11 | stmT8a0PA/lCNMrzJBzkLcuem1G1uMHoQZo1f3OpslJ8gHbE9ZlIbIKmpmJS9oop 12 | Vh1BH+aOy5doCrF8uOLTQ3d5CqA/EZMGahDHy7IkeNYmG/RXUKNltv+r95gwuRP+ 13 | 9UIJ9FTa4REQbIpGWP5XibI6x4LqLTJj+VsCAwEAAaNeMFwwHQYDVR0OBBYEFEKP 14 | y8hHZVazpvIsxFcGo4YrkEkwMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEF 15 | BQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jANBgkqhkiG9w0BAQsFAAOC 16 | AgEAMzTRDLBExVFlw98AuX+pM+/R2Gjw5KFHvSYLKLbMRfuuZK1yNYYaYtNrtF+V 17 | a53OFgaZj56o7tXc2PB8kw4MELD0ViR8Do2bvZieFcEe4DwhdjGCjuLehVLT29qI 18 | 7T3N/JkJ5daemKZcRB6Ne0F4+6QlVVNck28HUKbQThl88RdwLUImmSAfgKSt6uJ5 19 | wlH7wiYQR2vPXwSuEYzwot+L/91eBwuQr4Lovx9+TCKTbwQOKYjX4KfcOOQ1rx0M 20 | IMrvwWqnabc6m1F0O6//ibL0kuFkJYEgOH2uJA12FBHO+/q2tcytejkOWKWMJj6Y 21 | 2etwIHcpzXaEP7fZ75cFGqcE3s7XGsweBIPLjMP1bKxEcFKzygURm/auUuXBCFBl 22 | E16PB6JEAeCKe/8VFeyucvjPuQDWB49aq+r2SbpbI4IeZdz/QgEIOb0MpwStrvhH 23 | 9f/DtGMbjvuAEkRoOorK4m5k4GY3LsWTR2bey27AXk8N7pKarpu2N7ChBPm+EV0Y 24 | H+tAI/OfdZuNUCES00F5UAFdU8zBUZo19ao2ZqfEADimE7Epk2s0bUe4GSqEXJp6 25 | 68oVSMhZmMf/RCSNlr97f34sNiUA1YJ0JbCRZmw8KWNm9H1PARLbrgeRBZ/k31Li 26 | WLDr3fiEVk7SGxj3zo94cS6AT55DyXLiSD/bFmL1QXgZweA= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /tests/data/certificate.chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIBuDCCAWqgAwIBAgICAcgwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk 3 | RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE5MDgxNjEzMjg1MVoXDTI1MDIw 4 | NTEzMjg1MVowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wKjAFBgMrZXADIQAQ 5 | 9M4hrE+Ucw4QUmaKOeKfphklBJi1qsqtX4u+knbseqOBwDCBvTAMBgNVHRMBAf8E 6 | AjAAMAsGA1UdDwQEAwIGwDAdBgNVHQ4EFgQUa/gnV4+a22BUKTouAYX6nfLnPKYw 7 | RAYDVR0jBD0wO4AUFxIwU406tG3CsPWkHWqfuUT48auhIKQeMBwxGjAYBgNVBAMM 8 | EXBvbnl0b3duIEVkRFNBIENBggF7MDsGA1UdEQQ0MDKCDnRlc3RzZXJ2ZXIuY29t 9 | ghVzZWNvbmQudGVzdHNlcnZlci5jb22CCWxvY2FsaG9zdDAFBgMrZXADQQApDiBQ 10 | ns3fuvsWuFpIS+osj2B/gQ0b6eBAZ1UBxRyDlAo5++JZ0PtaEROyGo2t2gqi2Lyz 11 | 47mLyGCvqgVbC6cH 12 | -----END CERTIFICATE----- 13 | -----BEGIN CERTIFICATE----- 14 | MIIBVzCCAQmgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE 15 | U0EgQ0EwHhcNMTkwODE2MTMyODUxWhcNMjkwODEzMTMyODUxWjAuMSwwKgYDVQQD 16 | DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh 17 | AD4h3t0UCoMDGgIq4UW4P5zDngsY4vy1pE3wzLPFI4Vdo14wXDAdBgNVHQ4EFgQU 18 | FxIwU406tG3CsPWkHWqfuUT48aswIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG 19 | AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MAUGAytlcANBAAZFvMek 20 | Z71I8CXsBmx/0E6Weoaan9mJHgKqgQdK4w4h4dRg6DjNG957IbrLFO3vZduBMnna 21 | qHP3xTFF+11Eyg8= 22 | -----END CERTIFICATE----- 23 | -----BEGIN CERTIFICATE----- 24 | MIIBTDCB/6ADAgECAhRXcvbYynz4+usVvPtJp++sBUih3TAFBgMrZXAwHDEaMBgG 25 | A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMTkwODE2MTMyODUwWhcNMjkwODEz 26 | MTMyODUwWjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh 27 | AIE4tLweIfcBGfhPqyXFp5pjVxjaiKk+9fTbRy46jAFKo1MwUTAdBgNVHQ4EFgQU 28 | z5b9HjkOxffbtCZhWGg+bnxuD6wwHwYDVR0jBBgwFoAUz5b9HjkOxffbtCZhWGg+ 29 | bnxuD6wwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBNlt7z4bZ7KhzecxZEe3i5 30 | lH9MRqbpP9Rg4HyzAJfTzFGT183HoJiISdPLbxwMn0KaqSGlVe+9GgNKswoaRAwH 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /tests/data/rsa-key-no-trailing-newline.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCrOnJjNc/rt+EA 3 | 5V5w6Jq+d6WzukJqrFrEVogUI3ByMagTGx5YYABtbqnJXZ5Ad5gUxZzihqCvNzEP 4 | IPS4OGIfhvzKhRno1qIK4u/luoVdc/kItZ5gNE7OIW6hmgD6X1JRQjnc6GNoOINX 5 | 2wmL7zePhD6mCqNIwlqQjjDh+GHkPdlhn/XXnv/LgRYpb52lfVmihqcAej5mIr6g 6 | 5xdyV1ql53QwdOEx0IJayOVuCvVSt2l7y8vqolHQ4CwNc4XN+rTYXdia8akysFiH 7 | dMeUQi46TqWarvK+0s3hcq5NNzesg6p5miNywpUVffAYLmd6Rr9vDOkFTwQzIX8U 8 | 3Pi05TNtAgMBAAECggEALEZWoZSeiMLpKUQl4Wgj6zAg5pI47kBW0AHX+e0X/E8e 9 | uKIAfLWGJsXAnVLZwq2p42Udpe1Ny0CoLNNGtAPQS3qqDuvPwUcOa2Y2xd4u8fU0 10 | 5a2goBumbOJ0KtEZg6P2MxC+yYJFggNq4uK+WzKl2TRGxsRlaEDpDgvWU/fXyB5Q 11 | pkMVkHU8VWKtSG+rZ0nMZnGuRFKFdTmtLcTYJip4qY4EqqkmhNYdkQrBDOCGEU4o 12 | cV/Nx48iNs78SP06ZLl3rp0xxI436ZTFvPw6E1B3j0Bsh2ufYU37RtuiUTbXABkw 13 | X67Lt5evvomxWHovpD3zXg8dP8DEOBZMkc1HFJAIuQKBgQDxH3uWrAhCSEMvLB+2 14 | 1KGlpEe2V/Sq5c6I2+PqgKNU9/RdJIX0QiNZ5VQQGcBzWRv6HDWnrDWO7AxuQSA8 15 | golZhH3bMXe8k9XWk6yn+XZsnvR6XQ702LqUH6e9cX9VRjR1gCWhSc9OcnfruwWT 16 | penS3L94qxZi8lrQSKyS0R+7OwKBgQC1yvvMKF/FDoLmbch01ep7dbWUBfXp6oM/ 17 | HSzm3Q7U8cyQdwNcwc1zR8x7TFnuvyPxqV1K1hVbUot77s5yQ9x89EQNN682zEjT 18 | 7Ox8ELYj7RxLc2cl23/Rv8Exvotv2HvbA+vr9lI/UlyUsRmZFnn+2cAcKge+o1M6 19 | BTwTzT7RdwKBgHS45c3pV1ImwwcZ1/xccCfMH77gUxtLhbBwqaMCRI7EPTG9lW7J 20 | eW1x+0CUBrqP32AyubKCRab7E4Vn12ATXMPNxFMQXkMWWYS3FfR2aWJpjDWTyMK0 21 | C5XawQuO3rH7+zcKIq0yGr2B4hVmAmwX+9nMbI/QWlTptxZup8OmojKzAoGAL5sK 22 | OTpRaf8U1FbnaYEjOFVoyWyOK0VYPUzcl1BINAdl9GbWIJI3xPqGV7t0yYqQVRZu 23 | 8cwCJ7oEAN5WfaG7uZUVxQhR/92bLLQccZjGub277R45YraKUFkQtIAbb5yXQpFS 24 | VKZaf26IBAAknew+4jgPkNCI2qlWgBki1GSpEJ0CgYBpksYOxy58fVySeKtxz7Vy 25 | ckfVCsCsJKIRK3GU4M67GPiDUXGPJCb8l5QUcjwD1hte6aT6/z+DhW552kfcRd/w 26 | d9UuI3uBcZosFBI5UQM4oQQ5gOLOcSmHfesRE0X5hQdcgjN025lUxUTGJqsrD6wj 27 | gh5gNFkL4M5Hoktydw93eQ== 28 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /fuzz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "arbitrary" 7 | version = "1.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" 10 | 11 | [[package]] 12 | name = "cc" 13 | version = "1.0.90" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" 16 | dependencies = [ 17 | "jobserver", 18 | "libc", 19 | ] 20 | 21 | [[package]] 22 | name = "jobserver" 23 | version = "0.1.28" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" 26 | dependencies = [ 27 | "libc", 28 | ] 29 | 30 | [[package]] 31 | name = "libc" 32 | version = "0.2.153" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 35 | 36 | [[package]] 37 | name = "libfuzzer-sys" 38 | version = "0.4.7" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" 41 | dependencies = [ 42 | "arbitrary", 43 | "cc", 44 | "once_cell", 45 | ] 46 | 47 | [[package]] 48 | name = "once_cell" 49 | version = "1.19.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 52 | 53 | [[package]] 54 | name = "rustls-pki-types" 55 | version = "1.8.0" 56 | 57 | [[package]] 58 | name = "rustls-pki-types-fuzz" 59 | version = "0.0.0" 60 | dependencies = [ 61 | "libfuzzer-sys", 62 | "rustls-pki-types", 63 | ] 64 | -------------------------------------------------------------------------------- /tests/key_type.rs: -------------------------------------------------------------------------------- 1 | use rustls_pki_types::PrivateKeyDer; 2 | 3 | #[test] 4 | fn test_private_key_from_der() { 5 | const NIST_P256_KEY_SEC1: &[u8] = include_bytes!("../tests/keys/nistp256key.der"); 6 | const ECDSA_P256K1_KEY_SEC1: &[u8] = include_bytes!("../tests/keys/ecdsap256k1key.der"); 7 | const NIST_P384_KEY_SEC1: &[u8] = include_bytes!("../tests/keys/nistp384key.der"); 8 | const NIST_P521_KEY_SEC1: &[u8] = include_bytes!("../tests/keys/nistp521key.der"); 9 | for bytes in [ 10 | NIST_P256_KEY_SEC1, 11 | ECDSA_P256K1_KEY_SEC1, 12 | NIST_P384_KEY_SEC1, 13 | NIST_P521_KEY_SEC1, 14 | ] { 15 | assert!(matches!( 16 | PrivateKeyDer::try_from(bytes).unwrap(), 17 | PrivateKeyDer::Sec1(_) 18 | )); 19 | } 20 | 21 | const RSA_2048_KEY_PKCS1: &[u8] = include_bytes!("../tests/keys/rsa2048key.pkcs1.der"); 22 | assert!(matches!( 23 | PrivateKeyDer::try_from(RSA_2048_KEY_PKCS1).unwrap(), 24 | PrivateKeyDer::Pkcs1(_) 25 | )); 26 | 27 | const NIST_P256_KEY_PKCS8: &[u8] = include_bytes!("../tests/keys/nistp256key.pkcs8.der"); 28 | const ECDSA_P256K1_KEY_PKCS8: &[u8] = include_bytes!("../tests/keys/ecdsap256k1key.pkcs8.der"); 29 | const NIST_P384_KEY_PKCS8: &[u8] = include_bytes!("../tests/keys/nistp384key.pkcs8.der"); 30 | const NIST_P521_KEY_PKCS8: &[u8] = include_bytes!("../tests/keys/nistp521key.pkcs8.der"); 31 | const RSA_2048_KEY_PKCS8: &[u8] = include_bytes!("../tests/keys/rsa2048key.pkcs8.der"); 32 | const RSA_4096_KEY: &[u8] = include_bytes!("../tests/keys/rsa4096key.pkcs8.der"); 33 | const ED25519_KEY: &[u8] = include_bytes!("../tests/keys/edd25519_v2.der"); 34 | const ED448_KEY: &[u8] = include_bytes!("../tests/keys/ed448.pkcs8.der"); 35 | const PKCS8_KEYS: &[&[u8]] = &[ 36 | NIST_P256_KEY_PKCS8, 37 | ECDSA_P256K1_KEY_PKCS8, 38 | NIST_P384_KEY_PKCS8, 39 | NIST_P521_KEY_PKCS8, 40 | RSA_2048_KEY_PKCS8, 41 | RSA_4096_KEY, 42 | ED25519_KEY, 43 | ED448_KEY, 44 | ]; 45 | 46 | for &bytes in PKCS8_KEYS { 47 | assert!(matches!( 48 | PrivateKeyDer::try_from(bytes).unwrap(), 49 | PrivateKeyDer::Pkcs8(_) 50 | )); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustls-pki-types 2 | 3 | [![Build Status](https://github.com/rustls/pki-types/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/rustls/pki-types/actions/workflows/ci.yml?query=branch%3Amain) 4 | [![Documentation](https://docs.rs/rustls-pki-types/badge.svg)](https://docs.rs/rustls-pki-types/) 5 | [![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/MCSB76RU96) 6 | 7 | This crate provides types for representing X.509 certificates, keys and other types as commonly 8 | used in the rustls ecosystem. It is intended to be used by crates that need to work with such X.509 9 | types, such as [rustls](https://crates.io/crates/rustls), 10 | [rustls-webpki](https://crates.io/crates/rustls-webpki), 11 | and others. 12 | 13 | Some of these crates used to define their own trivial wrappers around DER-encoded bytes. 14 | However, in order to avoid inconvenient dependency edges, these were all disconnected. By 15 | using a common low-level crate of types with long-term stable API, we hope to avoid the 16 | downsides of unnecessary dependency edges while providing interoperability between crates. 17 | 18 | ## Features 19 | 20 | - Interoperability between different crates in the rustls ecosystem 21 | - Long-term stable API 22 | - No dependencies 23 | - Support for `no_std` contexts, with optional support for `alloc` 24 | 25 | ## DER and PEM 26 | 27 | Many of the types defined in this crate represent DER-encoded data. DER is a binary encoding of 28 | the ASN.1 format commonly used in web PKI specifications. It is a binary encoding, so it is 29 | relatively compact when stored in memory. However, as a binary format, it is not very easy to 30 | work with for humans and in contexts where binary data is inconvenient. For this reason, 31 | many tools and protocols use a ASCII-based encoding of DER, called PEM. In addition to the 32 | base64-encoded DER, PEM objects are delimited by header and footer lines which indicate the type 33 | of object contained in the PEM blob. 34 | 35 | This crate's types can be created from both DER and PEM encodings. 36 | 37 | ## Creating new certificates and keys 38 | 39 | This crate does not provide any functionality for creating new certificates or keys. However, 40 | the [rcgen](https://docs.rs/rcgen) crate can be used to create new certificates and keys. 41 | 42 | ## Cloning private keys 43 | 44 | This crate intentionally **does not** implement `Clone` on private key types in 45 | order to minimize the exposure of private key data in memory. 46 | 47 | If you want to extend the lifetime of a `PrivateKeyDer<'_>`, consider [`PrivateKeyDer::clone_key()`]. 48 | Alternatively since these types are immutable, consider wrapping the `PrivateKeyDer<'_>` in a [`Rc`] 49 | or an [`Arc`]. 50 | 51 | [`Rc`]: https://doc.rust-lang.org/std/rc/struct.Rc.html 52 | [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html 53 | [`PrivateKeyDer::clone_key()`]: https://docs.rs/rustls-pki-types/latest/rustls_pki_types/enum.PrivateKeyDer.html#method.clone_key 54 | -------------------------------------------------------------------------------- /tests/data/mixed-line-endings.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF 3 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 4 | b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL 5 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 6 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 7 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 8 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 9 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 10 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 11 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 12 | jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC 13 | AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA 14 | A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI 15 | U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs 16 | N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv 17 | o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 18 | 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy 19 | rqXRfboQnoZsG4q5WTP468SQvvG5 20 | -----END CERTIFICATE----- 21 | -----BEGIN CERTIFICATE----- MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy rqXRfboQnoZsG4q5WTP468SQvvG5 -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- 22 | MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF 23 | ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 24 | b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL 25 | MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv 26 | b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj 27 | ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 28 | 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw 29 | IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 30 | VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 31 | 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm 32 | jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC 33 | AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA 34 | A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI 35 | U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs 36 | N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv 37 | o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 38 | 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy 39 | rqXRfboQnoZsG4q5WTP468SQvvG5 40 | -----END CERTIFICATE----- 41 | -----BEGIN CERTIFICATE----- MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM 9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L 93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU 5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy rqXRfboQnoZsG4q5WTP468SQvvG5 -----END CERTIFICATE----- -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bumpalo" 7 | version = "3.17.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 10 | 11 | [[package]] 12 | name = "cc" 13 | version = "1.2.17" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" 16 | dependencies = [ 17 | "jobserver", 18 | "libc", 19 | "shlex", 20 | ] 21 | 22 | [[package]] 23 | name = "cfg-if" 24 | version = "1.0.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 27 | 28 | [[package]] 29 | name = "crabgrind" 30 | version = "0.1.9" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "bdbd43e4f32a9681a504577db2d4ea7d3f7b1bf2e97955561af98501ab600508" 33 | dependencies = [ 34 | "cc", 35 | ] 36 | 37 | [[package]] 38 | name = "jobserver" 39 | version = "0.1.32" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 42 | dependencies = [ 43 | "libc", 44 | ] 45 | 46 | [[package]] 47 | name = "js-sys" 48 | version = "0.3.77" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 51 | dependencies = [ 52 | "once_cell", 53 | "wasm-bindgen", 54 | ] 55 | 56 | [[package]] 57 | name = "libc" 58 | version = "0.2.171" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 61 | 62 | [[package]] 63 | name = "log" 64 | version = "0.4.27" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 67 | 68 | [[package]] 69 | name = "once_cell" 70 | version = "1.21.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" 73 | 74 | [[package]] 75 | name = "proc-macro2" 76 | version = "1.0.94" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 79 | dependencies = [ 80 | "unicode-ident", 81 | ] 82 | 83 | [[package]] 84 | name = "quote" 85 | version = "1.0.40" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 88 | dependencies = [ 89 | "proc-macro2", 90 | ] 91 | 92 | [[package]] 93 | name = "rustls-pki-types" 94 | version = "1.13.2" 95 | dependencies = [ 96 | "crabgrind", 97 | "web-time", 98 | "zeroize", 99 | ] 100 | 101 | [[package]] 102 | name = "shlex" 103 | version = "1.3.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 106 | 107 | [[package]] 108 | name = "syn" 109 | version = "2.0.100" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 112 | dependencies = [ 113 | "proc-macro2", 114 | "quote", 115 | "unicode-ident", 116 | ] 117 | 118 | [[package]] 119 | name = "unicode-ident" 120 | version = "1.0.18" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 123 | 124 | [[package]] 125 | name = "wasm-bindgen" 126 | version = "0.2.100" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 129 | dependencies = [ 130 | "cfg-if", 131 | "once_cell", 132 | "wasm-bindgen-macro", 133 | ] 134 | 135 | [[package]] 136 | name = "wasm-bindgen-backend" 137 | version = "0.2.100" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 140 | dependencies = [ 141 | "bumpalo", 142 | "log", 143 | "proc-macro2", 144 | "quote", 145 | "syn", 146 | "wasm-bindgen-shared", 147 | ] 148 | 149 | [[package]] 150 | name = "wasm-bindgen-macro" 151 | version = "0.2.100" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 154 | dependencies = [ 155 | "quote", 156 | "wasm-bindgen-macro-support", 157 | ] 158 | 159 | [[package]] 160 | name = "wasm-bindgen-macro-support" 161 | version = "0.2.100" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 164 | dependencies = [ 165 | "proc-macro2", 166 | "quote", 167 | "syn", 168 | "wasm-bindgen-backend", 169 | "wasm-bindgen-shared", 170 | ] 171 | 172 | [[package]] 173 | name = "wasm-bindgen-shared" 174 | version = "0.2.100" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 177 | dependencies = [ 178 | "unicode-ident", 179 | ] 180 | 181 | [[package]] 182 | name = "web-time" 183 | version = "1.1.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 186 | dependencies = [ 187 | "js-sys", 188 | "wasm-bindgen", 189 | ] 190 | 191 | [[package]] 192 | name = "zeroize" 193 | version = "1.8.1" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 196 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: rustls 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: ['main', 'ci/*'] 9 | pull_request: 10 | merge_group: 11 | schedule: 12 | - cron: '0 21 * * *' 13 | workflow_dispatch: 14 | 15 | jobs: 16 | build: 17 | name: Build + test 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | # test a bunch of toolchains on ubuntu 23 | rust: 24 | - stable 25 | - beta 26 | - nightly 27 | os: [ubuntu-latest] 28 | # but only stable on macos/windows (slower platforms) 29 | include: 30 | - os: macos-latest 31 | rust: stable 32 | - os: windows-latest 33 | rust: stable 34 | steps: 35 | - name: Checkout sources 36 | uses: actions/checkout@v4 37 | with: 38 | persist-credentials: false 39 | 40 | - name: Install ${{ matrix.rust }} toolchain 41 | uses: dtolnay/rust-toolchain@master 42 | with: 43 | toolchain: ${{ matrix.rust }} 44 | 45 | - name: Install valgrind 46 | if: runner.os == 'Linux' 47 | run: sudo apt-get update && sudo apt-get install -y valgrind 48 | 49 | - name: cargo test (debug; default features) 50 | run: cargo test --locked 51 | env: 52 | RUST_BACKTRACE: 1 53 | 54 | - name: cargo test (debug; all features) 55 | run: cargo test --locked --all-features 56 | env: 57 | RUST_BACKTRACE: 1 58 | 59 | - name: cargo test (debug; no default features; no run) 60 | run: cargo test --locked --no-default-features 61 | env: 62 | RUST_BACKTRACE: 1 63 | 64 | wasm_build: 65 | name: Build wasm32 66 | runs-on: ubuntu-latest 67 | steps: 68 | - name: Checkout sources 69 | uses: actions/checkout@v4 70 | with: 71 | persist-credentials: false 72 | 73 | - name: Install stable toolchain 74 | uses: dtolnay/rust-toolchain@stable 75 | 76 | - name: Add wasm target 77 | run: rustup target add wasm32-unknown-unknown 78 | 79 | - name: wasm32 build (debug; default features) 80 | run: cargo build --locked --target wasm32-unknown-unknown --lib 81 | env: 82 | RUST_BACKTRACE: 1 83 | 84 | - name: wasm32 build (debug; all features) 85 | run: cargo build --locked --target wasm32-unknown-unknown --lib --all-features 86 | env: 87 | RUST_BACKTRACE: 1 88 | 89 | - name: wasm32 build (debug; no default features) 90 | run: cargo build --locked --target wasm32-unknown-unknown --lib --no-default-features 91 | env: 92 | RUST_BACKTRACE: 1 93 | 94 | msrv: 95 | runs-on: ${{ matrix.os }} 96 | strategy: 97 | fail-fast: false 98 | matrix: 99 | os: [ubuntu-latest, macos-latest, windows-latest] 100 | steps: 101 | - name: Checkout sources 102 | uses: actions/checkout@v4 103 | with: 104 | persist-credentials: false 105 | 106 | - name: Install MSRV toolchain 107 | uses: dtolnay/rust-toolchain@master 108 | with: 109 | toolchain: "1.60" 110 | 111 | - run: cargo check --lib --locked --all-features 112 | 113 | format: 114 | name: Format 115 | runs-on: ubuntu-latest 116 | steps: 117 | - name: Checkout sources 118 | uses: actions/checkout@v4 119 | with: 120 | persist-credentials: false 121 | - name: Install rust toolchain 122 | uses: dtolnay/rust-toolchain@stable 123 | with: 124 | components: rustfmt 125 | - name: Check formatting 126 | run: cargo fmt --all -- --check 127 | 128 | clippy: 129 | name: Clippy 130 | runs-on: ubuntu-latest 131 | steps: 132 | - name: Checkout sources 133 | uses: actions/checkout@v4 134 | with: 135 | persist-credentials: false 136 | - name: Install rust toolchain 137 | uses: dtolnay/rust-toolchain@stable 138 | with: 139 | components: clippy 140 | - run: cargo clippy --locked --all-features -- --deny warnings 141 | 142 | semver: 143 | name: Check semver compatibility 144 | runs-on: ubuntu-latest 145 | steps: 146 | - name: Checkout sources 147 | uses: actions/checkout@v4 148 | with: 149 | persist-credentials: false 150 | 151 | - name: Check semver 152 | uses: obi1kenobi/cargo-semver-checks-action@v2 153 | 154 | check-external-types: 155 | name: Validate external types appearing in public API 156 | runs-on: ubuntu-latest 157 | steps: 158 | - name: Checkout sources 159 | uses: actions/checkout@v4 160 | with: 161 | persist-credentials: false 162 | - name: Install rust toolchain 163 | uses: dtolnay/rust-toolchain@master 164 | with: 165 | toolchain: nightly-2025-10-18 166 | # ^ sync with https://github.com/awslabs/cargo-check-external-types/blob/main/rust-toolchain.toml 167 | - name: Install cargo-check-external-types 168 | uses: taiki-e/cache-cargo-install-action@v2 169 | with: 170 | tool: cargo-check-external-types 171 | - name: run cargo-check-external-types 172 | run: cargo check-external-types 173 | 174 | fuzz: 175 | name: Smoke-test fuzzing targets 176 | runs-on: ubuntu-latest 177 | steps: 178 | - name: Checkout sources 179 | uses: actions/checkout@v4 180 | with: 181 | persist-credentials: false 182 | 183 | - name: Install nightly toolchain 184 | uses: dtolnay/rust-toolchain@nightly 185 | 186 | - name: Install cargo fuzz 187 | run: cargo install cargo-fuzz 188 | 189 | - name: Smoke-test fuzz targets 190 | run: | 191 | cargo fuzz build 192 | for target in $(cargo fuzz list) ; do 193 | cargo fuzz run $target -- -max_total_time=10 194 | done 195 | 196 | valgrind: 197 | name: Check side-channels on base64 decoder 198 | runs-on: ubuntu-latest 199 | steps: 200 | - name: Checkout sources 201 | uses: actions/checkout@v4 202 | with: 203 | persist-credentials: false 204 | 205 | - name: Install stable toolchain 206 | uses: dtolnay/rust-toolchain@stable 207 | 208 | - name: Install valgrind 209 | if: runner.os == 'Linux' 210 | run: sudo apt-get update && sudo apt-get install -y valgrind 211 | 212 | - name: Build and run test 213 | run: | 214 | cargo test --locked --all-features --lib 215 | exe=$(cargo test --all-features --no-run --message-format json | \ 216 | jq --slurp --raw-output '.[] | select(.reason == "compiler-artifact") | select(.target.name == "rustls_pki_types") | select(.profile.test) | .executable') 217 | valgrind --error-exitcode=99 --exit-on-first-error=yes $exe 218 | 219 | audit: 220 | runs-on: ubuntu-latest 221 | steps: 222 | - uses: actions/checkout@v4 223 | - uses: EmbarkStudios/cargo-deny-action@v2 224 | 225 | package: 226 | runs-on: ubuntu-latest 227 | steps: 228 | - name: Checkout sources 229 | uses: actions/checkout@v4 230 | with: 231 | persist-credentials: false 232 | 233 | - name: Install stable toolchain 234 | uses: dtolnay/rust-toolchain@stable 235 | - name: Check if all files are included in package that would be published 236 | run: | 237 | cargo package --all-features 238 | -------------------------------------------------------------------------------- /tests/data/zen.pem: -------------------------------------------------------------------------------- 1 | one with everything 2 | -----BEGIN CERTIFICATE----- 3 | MIIBuDCCAWqgAwIBAgICAcgwBQYDK2VwMC4xLDAqBgNVBAMMI3Bvbnl0b3duIEVk 4 | RFNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE5MDgxNjEzMjg1MVoXDTI1MDIw 5 | NTEzMjg1MVowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wKjAFBgMrZXADIQAQ 6 | 9M4hrE+Ucw4QUmaKOeKfphklBJi1qsqtX4u+knbseqOBwDCBvTAMBgNVHRMBAf8E 7 | AjAAMAsGA1UdDwQEAwIGwDAdBgNVHQ4EFgQUa/gnV4+a22BUKTouAYX6nfLnPKYw 8 | RAYDVR0jBD0wO4AUFxIwU406tG3CsPWkHWqfuUT48auhIKQeMBwxGjAYBgNVBAMM 9 | EXBvbnl0b3duIEVkRFNBIENBggF7MDsGA1UdEQQ0MDKCDnRlc3RzZXJ2ZXIuY29t 10 | ghVzZWNvbmQudGVzdHNlcnZlci5jb22CCWxvY2FsaG9zdDAFBgMrZXADQQApDiBQ 11 | ns3fuvsWuFpIS+osj2B/gQ0b6eBAZ1UBxRyDlAo5++JZ0PtaEROyGo2t2gqi2Lyz 12 | 47mLyGCvqgVbC6cH 13 | -----END CERTIFICATE----- 14 | -----BEGIN CERTIFICATE----- 15 | MIIBVzCCAQmgAwIBAgIBezAFBgMrZXAwHDEaMBgGA1UEAwwRcG9ueXRvd24gRWRE 16 | U0EgQ0EwHhcNMTkwODE2MTMyODUxWhcNMjkwODEzMTMyODUxWjAuMSwwKgYDVQQD 17 | DCNwb255dG93biBFZERTQSBsZXZlbCAyIGludGVybWVkaWF0ZTAqMAUGAytlcAMh 18 | AD4h3t0UCoMDGgIq4UW4P5zDngsY4vy1pE3wzLPFI4Vdo14wXDAdBgNVHQ4EFgQU 19 | FxIwU406tG3CsPWkHWqfuUT48aswIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsG 20 | AQUFBwMCMAwGA1UdEwQFMAMBAf8wCwYDVR0PBAQDAgH+MAUGAytlcANBAAZFvMek 21 | Z71I8CXsBmx/0E6Weoaan9mJHgKqgQdK4w4h4dRg6DjNG957IbrLFO3vZduBMnna 22 | qHP3xTFF+11Eyg8= 23 | -----END CERTIFICATE----- 24 | -----BEGIN CERTIFICATE----- 25 | MIIBTDCB/6ADAgECAhRXcvbYynz4+usVvPtJp++sBUih3TAFBgMrZXAwHDEaMBgG 26 | A1UEAwwRcG9ueXRvd24gRWREU0EgQ0EwHhcNMTkwODE2MTMyODUwWhcNMjkwODEz 27 | MTMyODUwWjAcMRowGAYDVQQDDBFwb255dG93biBFZERTQSBDQTAqMAUGAytlcAMh 28 | AIE4tLweIfcBGfhPqyXFp5pjVxjaiKk+9fTbRy46jAFKo1MwUTAdBgNVHQ4EFgQU 29 | z5b9HjkOxffbtCZhWGg+bnxuD6wwHwYDVR0jBBgwFoAUz5b9HjkOxffbtCZhWGg+ 30 | bnxuD6wwDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBNlt7z4bZ7KhzecxZEe3i5 31 | lH9MRqbpP9Rg4HyzAJfTzFGT183HoJiISdPLbxwMn0KaqSGlVe+9GgNKswoaRAwH 32 | -----END CERTIFICATE----- 33 | -----BEGIN ZEN MASTER JOKE----- 34 | QSBaZW4gbWFzdGVyIHZpc2l0aW5nIE5ldyBZb3JrIENpdHkgZ29lcyB1cCB0byBhIGhvdCBkb2cg 35 | dmVuZG9yIGFuZCBzYXlzLCAiTWFrZSBtZSBvbmUgd2l0aCBldmVyeXRoaW5nLiIKVGhlIGhvdCBk 36 | b2cgdmVuZG9yIGZpeGVzIGEgaG90IGRvZyBhbmQgaGFuZHMgaXQgdG8gdGhlIFplbiBtYXN0ZXIs 37 | IHdobyBwYXlzIHdpdGggYSAkMjAgYmlsbC4KVGhlIHZlbmRvciBwdXRzIHRoZSBiaWxsIGluIHRo 38 | ZSBjYXNoIGJveCBhbmQgY2xvc2VzIGl0LiAiRXhjdXNlIG1lLCBidXQgd2hlcmUncyBteSBjaGFu 39 | Z2U/IiBhc2tzIHRoZSBaZW4gbWFzdGVyLgpUaGUgdmVuZG9yIHJlc3BvbmRzLCAiQ2hhbmdlIG11 40 | c3QgY29tZSBmcm9tIHdpdGhpbi4iCg== 41 | -----END ZEN MASTER JOKE----- 42 | -----BEGIN CERTIFICATE----- 43 | MIIEnzCCAoegAwIBAgIBezANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9wb255 44 | dG93biBSU0EgQ0EwHhcNMTkwNjA5MTcxNTEyWhcNMjkwNjA2MTcxNTEyWjAsMSow 45 | KAYDVQQDDCFwb255dG93biBSU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwggGiMA0G 46 | CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCj/tOFeSW3WB+TtuLCR1L/84lZytFw 47 | zbpzOTGB1kPEKNbrMsv3lHXm5bHa8Bl3k113k7Hi7OAt/nkMm05s8LcUoovhaG5C 48 | G7tjzL+ld1nO74gNS3IQHCzxRdRwIgaDZHyICfBQBfB9/m+9z3yRtOKWJl6i/MT9 49 | HRN6yADW/8gHFlMzRkCKBjIKXehKsu8cbtB+5MukwtXI4rKf9aYXZQOEUn1kEwQJ 50 | ZIKBXR0eyloQiZervUE7meRCTBvzXT9VoSEX49/mempp4hnfdHlRNzre4/tphBf1 51 | fRUdpVXZ3DvmzoHdXRVzxx3X5LvDpf7Eb3ViGkXDFwkSfHEhkRnAl4lIzTH/1F25 52 | stmT8a0PA/lCNMrzJBzkLcuem1G1uMHoQZo1f3OpslJ8gHbE9ZlIbIKmpmJS9oop 53 | Vh1BH+aOy5doCrF8uOLTQ3d5CqA/EZMGahDHy7IkeNYmG/RXUKNltv+r95gwuRP+ 54 | 9UIJ9FTa4REQbIpGWP5XibI6x4LqLTJj+VsCAwEAAaNeMFwwHQYDVR0OBBYEFEKP 55 | y8hHZVazpvIsxFcGo4YrkEkwMCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEF 56 | BQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jANBgkqhkiG9w0BAQsFAAOC 57 | AgEAMzTRDLBExVFlw98AuX+pM+/R2Gjw5KFHvSYLKLbMRfuuZK1yNYYaYtNrtF+V 58 | a53OFgaZj56o7tXc2PB8kw4MELD0ViR8Do2bvZieFcEe4DwhdjGCjuLehVLT29qI 59 | 7T3N/JkJ5daemKZcRB6Ne0F4+6QlVVNck28HUKbQThl88RdwLUImmSAfgKSt6uJ5 60 | wlH7wiYQR2vPXwSuEYzwot+L/91eBwuQr4Lovx9+TCKTbwQOKYjX4KfcOOQ1rx0M 61 | IMrvwWqnabc6m1F0O6//ibL0kuFkJYEgOH2uJA12FBHO+/q2tcytejkOWKWMJj6Y 62 | 2etwIHcpzXaEP7fZ75cFGqcE3s7XGsweBIPLjMP1bKxEcFKzygURm/auUuXBCFBl 63 | E16PB6JEAeCKe/8VFeyucvjPuQDWB49aq+r2SbpbI4IeZdz/QgEIOb0MpwStrvhH 64 | 9f/DtGMbjvuAEkRoOorK4m5k4GY3LsWTR2bey27AXk8N7pKarpu2N7ChBPm+EV0Y 65 | H+tAI/OfdZuNUCES00F5UAFdU8zBUZo19ao2ZqfEADimE7Epk2s0bUe4GSqEXJp6 66 | 68oVSMhZmMf/RCSNlr97f34sNiUA1YJ0JbCRZmw8KWNm9H1PARLbrgeRBZ/k31Li 67 | WLDr3fiEVk7SGxj3zo94cS6AT55DyXLiSD/bFmL1QXgZweA= 68 | -----END CERTIFICATE----- 69 | -----BEGIN NOT SUPPORTED----- 70 | This is not required to be valid base64, it should be exactly 71 | ignored. 72 | -----END NOT SUPPORTED----- 73 | -----BEGIN EC PARAMETERS----- 74 | BggqhkjOPQMBBw== 75 | -----END EC PARAMETERS----- 76 | -----BEGIN EC PRIVATE KEY----- 77 | MHcCAQEEIMaA7bFrjDDBSik057bIKo7UQXJZNwLK9AjYZQ7yIWFloAoGCCqGSM49 78 | AwEHoUQDQgAExu0Z/w8nQJZAXeOXOnZun9HiZscY9H/KwYcXpeZHu+f9P9mOUEkH 79 | 5Z0av+JKtzhFspjngNLVgWcjlA1L5AJLdA== 80 | -----END EC PRIVATE KEY----- 81 | -----BEGIN PRIVATE KEY----- 82 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxoDtsWuMMMFKKTTn 83 | tsgqjtRBclk3Asr0CNhlDvIhYWWhRANCAATG7Rn/DydAlkBd45c6dm6f0eJmxxj0 84 | f8rBhxel5ke75/0/2Y5QSQflnRq/4kq3OEWymOeA0tWBZyOUDUvkAkt0 85 | -----END PRIVATE KEY----- 86 | -----BEGIN PUBLIC KEY----- 87 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqIh8FTj9DIgI8DAoCBh+ 88 | 6UXOfaWkvNaGZx2GwXl4WDAa/ZSE5/8ofg/6V59bmk9yry57UR4F+blscBvE4g3U 89 | dTvWJOBRD900l21vwpDLKzZguyGOCmKwJu3vCnAQKzBRXW5sDgvO67GeU6kpaic9 90 | LYPYnYaoxCRTYTZu0wy72rW5G0Fe8Gg/duJmUH7vqGIZupTTVzIBMbFVPBMJqprT 91 | MStDhaUL0JiAz0ZgTeNLRIBZWV9mY4PG3rZtbV0BZGR1ipAq9xfgqJcURCcKl/ZT 92 | UMtzvgk8s5hYkIJX0ZL3qsfdM4BMgIFhHq/GisQKbbu9kWldBrxQylOwa6r0m3Jv 93 | KJX2ViDSORndaCz2sppmVx5HDHnj+Bw381yawphnpumP3BJK4iof//uYKvfdc4RC 94 | y2EXL8PYPsT5DMB0jaBt92ytR5sLhn8Sl9Hk0buN4IjrYPISrdhS45xQXUqxcp9O 95 | 9hcU+rSaQyZ45cj+VlWhKq8MDvGvaAONBFSEh01mnUwoJObsAZNVFVtuOkwAli0F 96 | kGouMycQY1BGscpdC516Nya361Hk/ICyby2Y0BJrrVGaSM6poXH9yEjglzAdtSDb 97 | Cvhn/zlAI5ltm4Nv2qTgYBDns5JRGVhBym6RbbZ1C/KfCgn0hOxiw3N7AN4d0K5n 98 | LI6p7U9RnNVbWgbqsuoxBtkCAwEAAQ== 99 | -----END PUBLIC KEY----- 100 | -----BEGIN RSA PRIVATE KEY----- 101 | MIICXAIBAAKBgQC1Dt8tFmGS76ciuNXvk/QRrV8wCcArWxvl7Ku0aSQXgcFBAav6 102 | P5RD8b+dC9DihSu/r+6OOfjsAZ6oKCq3OTUfmoUhLpoBomxPczJgLyyLD+nQkp5q 103 | B1Q3WB6ACL/HJRRjJEIn7lc5u1FVBGbiCAHKMiaP4BDSym8oqimKC6uiaQIDAQAB 104 | AoGAGKmY7sxQqDIqwwkIYyT1Jv9FqwZ4/a7gYvZVATMdLnKHP3KZ2XGVoZepcRvt 105 | 7R0Us3ykcw0kgglKcj9eaizJtnSuoDPPwt53mDypPN2sU3hZgyk2tPgr49DB3MIp 106 | fjoqw4RL/p60ksgGXbDEqBuXqOtH5i61khWlMj+BWL9VDq0CQQDaELWPQGjgs+7X 107 | /QyWMJwOF4FXE4jecH/CcPVDB9K1ukllyC1HqTNe44Sp2bIDuSXXWb8yEixrEWBE 108 | ci2CSSjXAkEA1I4W9IzwEmAeLtL6VBip9ks52O0JKu373/Xv1F2GYdhnQaFw7IC6 109 | 1lSzcYMKGTmDuM8Cj26caldyv19Q0SPmvwJAdRHjZzS9GWWAJJTF3Rvbq/USix0B 110 | renXrRvXkFTy2n1YSjxdkstTuO2Mm2M0HquXlTWpX8hB8HkzpYtmwztjoQJAECKl 111 | LXVReCOhxu4vIJkqtc6qGoSL8J1WRH8X8KgU3nKeDAZkWx++jyyo3pIS/y01iZ71 112 | U8wSxaPTyyFCMk4mYwJBALjg7g8yDy1Lg9GFfOZvAVzPjqD28jZh/VJsDz9IhYoG 113 | z89iHWHkllOisbOm+SeynVC8CoFXmJPc26U65GcjI18= 114 | -----END RSA PRIVATE KEY----- 115 | -----BEGIN PRIVATE KEY----- 116 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALUO3y0WYZLvpyK4 117 | 1e+T9BGtXzAJwCtbG+Xsq7RpJBeBwUEBq/o/lEPxv50L0OKFK7+v7o45+OwBnqgo 118 | Krc5NR+ahSEumgGibE9zMmAvLIsP6dCSnmoHVDdYHoAIv8clFGMkQifuVzm7UVUE 119 | ZuIIAcoyJo/gENLKbyiqKYoLq6JpAgMBAAECgYAYqZjuzFCoMirDCQhjJPUm/0Wr 120 | Bnj9ruBi9lUBMx0ucoc/cpnZcZWhl6lxG+3tHRSzfKRzDSSCCUpyP15qLMm2dK6g 121 | M8/C3neYPKk83axTeFmDKTa0+Cvj0MHcwil+OirDhEv+nrSSyAZdsMSoG5eo60fm 122 | LrWSFaUyP4FYv1UOrQJBANoQtY9AaOCz7tf9DJYwnA4XgVcTiN5wf8Jw9UMH0rW6 123 | SWXILUepM17jhKnZsgO5JddZvzISLGsRYERyLYJJKNcCQQDUjhb0jPASYB4u0vpU 124 | GKn2SznY7Qkq7fvf9e/UXYZh2GdBoXDsgLrWVLNxgwoZOYO4zwKPbpxqV3K/X1DR 125 | I+a/AkB1EeNnNL0ZZYAklMXdG9ur9RKLHQGt6detG9eQVPLafVhKPF2Sy1O47Yyb 126 | YzQeq5eVNalfyEHweTOli2bDO2OhAkAQIqUtdVF4I6HG7i8gmSq1zqoahIvwnVZE 127 | fxfwqBTecp4MBmRbH76PLKjekhL/LTWJnvVTzBLFo9PLIUIyTiZjAkEAuODuDzIP 128 | LUuD0YV85m8BXM+OoPbyNmH9UmwPP0iFigbPz2IdYeSWU6Kxs6b5J7KdULwKgVeY 129 | k9zbpTrkZyMjXw== 130 | -----END PRIVATE KEY----- 131 | -----BEGIN X509 CRL----- 132 | MIICiTBzAgEBMA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNVBAMMD3Bvbnl0b3duIFJT 133 | QSBDQRcNMjMwNjI3MDgyODEyWhcNMjMwNzI3MDgyODEyWjAVMBMCAgHIFw0yMzA2 134 | MjcwODI3NTlaoA4wDDAKBgNVHRQEAwIBAjANBgkqhkiG9w0BAQsFAAOCAgEAP6EX 135 | 9+hxjx/AqdBpynZXjGkEqigBcLcJ2PADOXngdQI1jC0WuYnZymUimemeULtt8X+1 136 | ai2KxAuF1m4NEKZsrGKvO+/9s/X1xbGroyHSAMKtZafFopFpoB2aNbYlx7yIyLtD 137 | BBIZIF50g20U+3izqpHutTD10itdk9TLsSceJHpwTkNJtaWMkOfBV28nKzEzVutV 138 | f6WzRpURGzui6nQy7aIqImeanpoBoz323psMfC32U0uMBCZltyHNqsX58/2Uhucx 139 | 0IPnitNuhv4scCPf/jeRfGIWDrTf1/25LDzRxyg1S4z9aa+3GM4O3dqy4igZEhgT 140 | q3pjlJ2hUL5E0oqbZDIQD1SN8UUUv5N2AjwZcxVBNnYeGyuO7YpTBYiu62o73iL2 141 | CjgElfaMq/9hEr9GR9kJozh7VTxtQPbnr4DiucQvhv8o/A1z+zkC0gj8iCLFtDbO 142 | 8bvDowcdle9LKkrLaBe6sO+fSH/I9Wj8vrEJKsuwaEraIdEaq2VrIMUPEWN0/MH9 143 | vTwHyadGSMK4CWtrn9fCAgSLw6NX74D7Cx1IaS8vstMjpeUqOS0dk5ThiW47HceB 144 | DTko7rV5N+RGH2nW1ynLoZKCJQqqZcLilFMyKPui3jifJnQlMFi54jGVgg/D6UQn 145 | 7dA7wb2ux/1hSiaarp+mi7ncVOyByz6/WQP8mfc= 146 | -----END X509 CRL----- 147 | -----BEGIN CERTIFICATE REQUEST----- 148 | MIIC+zCCAeMCAQAwfDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRQwEgYD 149 | VQQHDAtTYW4gQW50b25pbzEdMBsGA1UECgwURXhhbXBsZSBPcmdhbml6YXRpb24x 150 | EjAQBgNVBAsMCU1hcmtldGluZzEUMBIGA1UEAwwLZXhhbXBsZS5jb20wggEiMA0G 151 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCwJw0BcbuqZyiABlmSYTi1tcr8DB0D 152 | NcTtzsYe7tlyIKd3mEs+u6Pi3rEQGvOw5eo6CmWII2qmVOqJ2f6gjl2lZJ5DUE6B 153 | I+NNE73zfFMrttUtI8X4ChnE4rrGqqUsSvYz1YVU0KiJ/00YMjEY5XlJYYa9FgfZ 154 | sUrhj4aCFdXS6CU9jueRr+udEBElDcgTS9+pB+LFhVfUMTdxnJ3BcT4ZDDqODH3/ 155 | 5RAgq03dhRpkkaVIg2uVKTBDoM3hs8T1zIxLM7hItaZzMv4uHdfI8y+BdHrePT33 156 | BoTlocvTEZEqqXEdw2kUd4PDgyUTjFE3b9OeLk0Ju5GRvuCW3UcS5gFvAgMBAAGg 157 | OjA4BgkqhkiG9w0BCQ4xKzApMCcGA1UdEQQgMB6CC2V4YW1wbGUuY29tgg9mb28u 158 | ZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADggEBACWsgxPw13QUpoCJOqvp8B1A 159 | EfsxRJITSROmukV3ZQycPT76Y3GVrM9sGjO8p13J/CVw2KcWc9xmgHF0MdvPNhnW 160 | OB6Y07hVpNnJVHb1KglOkNkTy6sVDtnZHg2klqGSyzIbwZ9R3JG8HtRdkceIrm3D 161 | gdiZyLcf1VDCCUGaskEi2CsggCQQJNyGi+8BSQ8MPKm/m0KrSchGQ157eWCCjopz 162 | f5GQe2UGOg5T7g8+S4GdECMwkMlTGUwlAM6LuOG/NZqP528PCAYQv0eOYdSwALQT 163 | GwTyU4AZ9y1uBFuaFxABew9GbDEtNY/XHTF8308edUwGBk6jfD+UuTeEwRZGs9E= 164 | -----END CERTIFICATE REQUEST----- 165 | -----BEGIN ECHCONFIG----- 166 | AD7+DQA65wAgACA8wVN2BtscOl3vQheUzHeIkVmKIiydUhDCliA4iyQRCwAEAAEA 167 | AQALZXhhbXBsZS5jb20AAA== 168 | -----END ECHCONFIG----- 169 | ... that's all folks! 170 | -------------------------------------------------------------------------------- /tests/pem.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "std")] 2 | 3 | use std::io::Cursor; 4 | 5 | use rustls_pki_types::pem::PemObject; 6 | use rustls_pki_types::{ 7 | CertificateDer, CertificateRevocationListDer, CertificateSigningRequestDer, EchConfigListBytes, 8 | PrivateKeyDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, PrivateSec1KeyDer, 9 | SubjectPublicKeyInfoDer, pem, 10 | }; 11 | 12 | #[test] 13 | fn pkcs1_private_key() { 14 | let data = include_bytes!("data/zen.pem"); 15 | 16 | PrivatePkcs1KeyDer::from_pem_slice(data).unwrap(); 17 | PrivatePkcs1KeyDer::from_pem_reader(&mut Cursor::new(&data[..])).unwrap(); 18 | PrivatePkcs1KeyDer::from_pem_file("tests/data/zen.pem").unwrap(); 19 | 20 | assert!(matches!( 21 | PrivatePkcs1KeyDer::from_pem_file("tests/data/certificate.chain.pem").unwrap_err(), 22 | pem::Error::NoItemsFound 23 | )); 24 | } 25 | 26 | #[test] 27 | fn pkcs8_private_key() { 28 | let data = include_bytes!("data/zen.pem"); 29 | 30 | PrivatePkcs8KeyDer::from_pem_slice(data).unwrap(); 31 | PrivatePkcs8KeyDer::from_pem_reader(&mut Cursor::new(&data[..])).unwrap(); 32 | PrivatePkcs8KeyDer::from_pem_file("tests/data/zen.pem").unwrap(); 33 | 34 | assert!(matches!( 35 | PrivatePkcs8KeyDer::from_pem_file("tests/data/certificate.chain.pem").unwrap_err(), 36 | pem::Error::NoItemsFound 37 | )); 38 | } 39 | 40 | #[test] 41 | fn sec1_private_key() { 42 | let data = include_bytes!("data/zen.pem"); 43 | 44 | PrivateSec1KeyDer::from_pem_slice(data).unwrap(); 45 | PrivateSec1KeyDer::from_pem_reader(&mut Cursor::new(&data[..])).unwrap(); 46 | PrivateSec1KeyDer::from_pem_file("tests/data/zen.pem").unwrap(); 47 | 48 | assert!(matches!( 49 | PrivateSec1KeyDer::from_pem_file("tests/data/certificate.chain.pem").unwrap_err(), 50 | pem::Error::NoItemsFound 51 | )); 52 | } 53 | 54 | #[test] 55 | fn any_private_key() { 56 | let data = include_bytes!("data/zen.pem"); 57 | 58 | PrivateKeyDer::from_pem_slice(data).unwrap(); 59 | PrivateKeyDer::from_pem_reader(&mut Cursor::new(&data[..])).unwrap(); 60 | PrivateKeyDer::from_pem_file("tests/data/zen.pem").unwrap(); 61 | 62 | for other_file in [ 63 | "tests/data/nistp256key.pem", 64 | "tests/data/nistp256key.pkcs8.pem", 65 | "tests/data/rsa1024.pkcs1.pem", 66 | "tests/data/rsa1024.pkcs8.pem", 67 | ] { 68 | PrivateKeyDer::from_pem_file(other_file).unwrap(); 69 | } 70 | 71 | assert!(matches!( 72 | PrivateKeyDer::from_pem_file("tests/data/certificate.chain.pem").unwrap_err(), 73 | pem::Error::NoItemsFound 74 | )); 75 | } 76 | 77 | #[test] 78 | fn no_trailing_newline() { 79 | let data = include_bytes!("data/rsa-key-no-trailing-newline.pem"); 80 | assert_eq!( 81 | PrivatePkcs8KeyDer::pem_slice_iter(data) 82 | .collect::, _>>() 83 | .unwrap() 84 | .len(), 85 | 1 86 | ); 87 | 88 | assert_eq!( 89 | PrivatePkcs8KeyDer::pem_file_iter("tests/data/rsa-key-no-trailing-newline.pem") 90 | .unwrap() 91 | .collect::, _>>() 92 | .unwrap() 93 | .len(), 94 | 1 95 | ); 96 | } 97 | 98 | #[test] 99 | fn certificates() { 100 | let data = include_bytes!("data/zen.pem"); 101 | 102 | assert_eq!( 103 | CertificateDer::pem_slice_iter(data) 104 | .collect::, _>>() 105 | .unwrap() 106 | .len(), 107 | 4 108 | ); 109 | assert_eq!( 110 | CertificateDer::pem_reader_iter(&mut Cursor::new(&data[..])) 111 | .collect::, _>>() 112 | .unwrap() 113 | .len(), 114 | 4 115 | ); 116 | assert_eq!( 117 | CertificateDer::pem_file_iter("tests/data/zen.pem") 118 | .unwrap() 119 | .count(), 120 | 4 121 | ); 122 | 123 | assert!(matches!( 124 | CertificateDer::from_pem_file("tests/data/crl.pem").unwrap_err(), 125 | pem::Error::NoItemsFound 126 | )); 127 | 128 | assert_eq!( 129 | CertificateDer::pem_file_iter("tests/data/certificate.chain.pem") 130 | .unwrap() 131 | .count(), 132 | 3 133 | ); 134 | } 135 | 136 | #[test] 137 | fn public_keys() { 138 | let data = include_bytes!("data/spki.pem"); 139 | 140 | SubjectPublicKeyInfoDer::from_pem_slice(data).unwrap(); 141 | SubjectPublicKeyInfoDer::from_pem_reader(&mut Cursor::new(&data[..])).unwrap(); 142 | SubjectPublicKeyInfoDer::from_pem_file("tests/data/spki.pem").unwrap(); 143 | 144 | assert!(matches!( 145 | SubjectPublicKeyInfoDer::from_pem_file("tests/data/certificate.chain.pem").unwrap_err(), 146 | pem::Error::NoItemsFound 147 | )); 148 | } 149 | 150 | #[test] 151 | fn csr() { 152 | let data = include_bytes!("data/zen.pem"); 153 | 154 | CertificateSigningRequestDer::from_pem_slice(data).unwrap(); 155 | CertificateSigningRequestDer::from_pem_reader(&mut Cursor::new(&data[..])).unwrap(); 156 | CertificateSigningRequestDer::from_pem_file("tests/data/zen.pem").unwrap(); 157 | 158 | assert!(matches!( 159 | CertificateSigningRequestDer::from_pem_file("tests/data/certificate.chain.pem") 160 | .unwrap_err(), 161 | pem::Error::NoItemsFound 162 | )); 163 | } 164 | 165 | #[test] 166 | fn crls() { 167 | let data = include_bytes!("data/zen.pem"); 168 | 169 | assert_eq!( 170 | CertificateRevocationListDer::pem_slice_iter(data) 171 | .collect::, _>>() 172 | .unwrap() 173 | .len(), 174 | 1 175 | ); 176 | assert_eq!( 177 | CertificateRevocationListDer::pem_reader_iter(&mut Cursor::new(&data[..])) 178 | .collect::, _>>() 179 | .unwrap() 180 | .len(), 181 | 1 182 | ); 183 | assert_eq!( 184 | CertificateRevocationListDer::pem_file_iter("tests/data/zen.pem") 185 | .unwrap() 186 | .count(), 187 | 1 188 | ); 189 | 190 | assert!(matches!( 191 | CertificateRevocationListDer::pem_file_iter("tests/data/certificate.chain.pem") 192 | .unwrap() 193 | .count(), 194 | 0 195 | )); 196 | 197 | assert_eq!( 198 | CertificateRevocationListDer::pem_file_iter("tests/data/crl.pem") 199 | .unwrap() 200 | .count(), 201 | 1 202 | ); 203 | } 204 | 205 | #[test] 206 | fn ech_config() { 207 | let data = include_bytes!("data/zen.pem"); 208 | 209 | EchConfigListBytes::from_pem_slice(data).unwrap(); 210 | EchConfigListBytes::from_pem_reader(&mut Cursor::new(&data[..])).unwrap(); 211 | EchConfigListBytes::from_pem_file("tests/data/zen.pem").unwrap(); 212 | 213 | assert!(matches!( 214 | EchConfigListBytes::from_pem_file("tests/data/certificate.chain.pem").unwrap_err(), 215 | pem::Error::NoItemsFound 216 | )); 217 | 218 | let (config, key) = EchConfigListBytes::config_and_key_from_iter( 219 | PemObject::pem_file_iter("tests/data/ech.pem").unwrap(), 220 | ) 221 | .unwrap(); 222 | println!("{config:?} {key:?}"); 223 | 224 | assert!(matches!( 225 | EchConfigListBytes::config_and_key_from_iter( 226 | PemObject::pem_file_iter("tests/data/certificate.chain.pem").unwrap(), 227 | ) 228 | .unwrap_err(), 229 | pem::Error::NoItemsFound, 230 | )); 231 | } 232 | 233 | #[test] 234 | fn certificates_with_binary() { 235 | let data = include_bytes!("data/gunk.pem"); 236 | 237 | assert_eq!( 238 | CertificateDer::pem_slice_iter(data) 239 | .collect::, _>>() 240 | .unwrap() 241 | .len(), 242 | 2 243 | ); 244 | assert_eq!( 245 | CertificateDer::pem_reader_iter(&mut Cursor::new(&data[..])) 246 | .collect::, _>>() 247 | .unwrap() 248 | .len(), 249 | 2 250 | ); 251 | assert_eq!( 252 | CertificateDer::pem_file_iter("tests/data/gunk.pem") 253 | .unwrap() 254 | .count(), 255 | 2 256 | ); 257 | } 258 | 259 | #[test] 260 | fn parse_in_order() { 261 | let data = include_bytes!("data/zen.pem"); 262 | let items = <(pem::SectionKind, Vec) as PemObject>::pem_slice_iter(data) 263 | .collect::, _>>() 264 | .unwrap(); 265 | assert_eq!(items.len(), 12); 266 | assert!(matches!(items[0], (pem::SectionKind::Certificate, _))); 267 | assert!(matches!(items[1], (pem::SectionKind::Certificate, _))); 268 | assert!(matches!(items[2], (pem::SectionKind::Certificate, _))); 269 | assert!(matches!(items[3], (pem::SectionKind::Certificate, _))); 270 | assert!(matches!(items[4], (pem::SectionKind::EcPrivateKey, _))); 271 | assert!(matches!(items[5], (pem::SectionKind::PrivateKey, _))); 272 | assert!(matches!(items[6], (pem::SectionKind::PublicKey, _))); 273 | assert!(matches!(items[7], (pem::SectionKind::RsaPrivateKey, _))); 274 | assert!(matches!(items[8], (pem::SectionKind::PrivateKey, _))); 275 | assert!(matches!(items[9], (pem::SectionKind::Crl, _))); 276 | assert!(matches!(items[10], (pem::SectionKind::Csr, _))); 277 | assert!(matches!(items[11], (pem::SectionKind::EchConfigList, _))); 278 | } 279 | 280 | #[test] 281 | fn whitespace_prefix() { 282 | CertificateDer::from_pem_file("tests/data/whitespace-prefix.crt").unwrap(); 283 | } 284 | 285 | #[test] 286 | fn different_line_endings() { 287 | let data = include_bytes!("data/mixed-line-endings.crt"); 288 | 289 | // Ensure non-LF line endings are not lost by mistake, causing the test 290 | // to silently regress. 291 | let mut contained_unix_ending = false; 292 | let mut contained_other_ending = false; 293 | for byte in data.iter().copied() { 294 | if contained_other_ending && contained_unix_ending { 295 | break; 296 | } 297 | 298 | if byte == b'\n' { 299 | contained_unix_ending = true; 300 | } else if byte == b'\r' { 301 | contained_other_ending = true; 302 | } 303 | } 304 | assert!(contained_unix_ending); 305 | assert!(contained_other_ending); 306 | 307 | assert_eq!( 308 | CertificateDer::pem_slice_iter(data) 309 | .collect::, _>>() 310 | .unwrap() 311 | .len(), 312 | 4 313 | ); 314 | assert_eq!( 315 | CertificateDer::pem_file_iter("tests/data/mixed-line-endings.crt") 316 | .unwrap() 317 | .count(), 318 | 4 319 | ); 320 | } 321 | 322 | #[test] 323 | fn slice_iterator() { 324 | let slice = b"hello\n-----BEGIN CERTIFICATE-----\naGk=\n-----END CERTIFICATE-----\ngoodbye\n"; 325 | 326 | let mut iter = CertificateDer::pem_slice_iter(slice); 327 | assert_eq!(iter.remainder(), slice); 328 | assert_eq!( 329 | iter.next().unwrap().unwrap(), 330 | CertificateDer::from(&b"hi"[..]) 331 | ); 332 | assert_eq!(iter.remainder(), b"goodbye\n"); 333 | assert!(iter.next().is_none()); 334 | } 335 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 Dirkjan Ochtman 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/alg_id.rs: -------------------------------------------------------------------------------- 1 | //! The PKIX [`AlgorithmIdentifier`] type, and common values. 2 | //! 3 | //! If you need to use an [`AlgorithmIdentifier`] not defined here, 4 | //! you can define it locally. 5 | 6 | use core::fmt; 7 | use core::ops::Deref; 8 | 9 | // See src/data/README.md. 10 | 11 | /// AlgorithmIdentifier for `id-ml-dsa-44`. 12 | /// 13 | /// This is: 14 | /// 15 | /// ```text 16 | /// OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.3.17 } 17 | /// ``` 18 | /// 19 | /// 20 | pub const ML_DSA_44: AlgorithmIdentifier = 21 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ml-dsa-44.der")); 22 | 23 | /// AlgorithmIdentifier for `id-ml-dsa-65`. 24 | /// 25 | /// This is: 26 | /// 27 | /// ```text 28 | /// OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.3.18 } 29 | /// ``` 30 | /// 31 | /// 32 | pub const ML_DSA_65: AlgorithmIdentifier = 33 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ml-dsa-65.der")); 34 | 35 | /// AlgorithmIdentifier for `id-ml-dsa-87`. 36 | /// 37 | /// This is: 38 | /// 39 | /// ```text 40 | /// OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.3.19 } 41 | /// ``` 42 | /// 43 | /// 44 | pub const ML_DSA_87: AlgorithmIdentifier = 45 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ml-dsa-87.der")); 46 | 47 | /// AlgorithmIdentifier for `id-ecPublicKey` with named curve `secp256k1`. 48 | /// 49 | /// This is: 50 | /// 51 | /// ```text 52 | /// # ecPublicKey 53 | /// OBJECT_IDENTIFIER { 1.2.840.10045.2.1 } 54 | /// # secp256k1 55 | /// OBJECT_IDENTIFIER { 1.3.132.0.10 } 56 | /// ``` 57 | pub const ECDSA_P256K1: AlgorithmIdentifier = 58 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ecdsa-p256k1.der")); 59 | 60 | /// AlgorithmIdentifier for `id-ecPublicKey` with named curve `secp256r1`. 61 | /// 62 | /// This is: 63 | /// 64 | /// ```text 65 | /// # ecPublicKey 66 | /// OBJECT_IDENTIFIER { 1.2.840.10045.2.1 } 67 | /// # secp256r1 68 | /// OBJECT_IDENTIFIER { 1.2.840.10045.3.1.7 } 69 | /// ``` 70 | pub const ECDSA_P256: AlgorithmIdentifier = 71 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ecdsa-p256.der")); 72 | 73 | /// AlgorithmIdentifier for `id-ecPublicKey` with named curve `secp384r1`. 74 | /// 75 | /// This is: 76 | /// 77 | /// ```text 78 | /// # ecPublicKey 79 | /// OBJECT_IDENTIFIER { 1.2.840.10045.2.1 } 80 | /// # secp384r1 81 | /// OBJECT_IDENTIFIER { 1.3.132.0.34 } 82 | /// ``` 83 | pub const ECDSA_P384: AlgorithmIdentifier = 84 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ecdsa-p384.der")); 85 | 86 | /// AlgorithmIdentifier for `id-ecPublicKey` with named curve `secp521r1`. 87 | /// 88 | /// This is: 89 | /// 90 | /// ```text 91 | /// # ecPublicKey 92 | /// OBJECT_IDENTIFIER { 1.2.840.10045.2.1 } 93 | /// # secp521r1 94 | /// OBJECT_IDENTIFIER { 1.3.132.0.35 } 95 | /// ``` 96 | pub const ECDSA_P521: AlgorithmIdentifier = 97 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ecdsa-p521.der")); 98 | 99 | /// AlgorithmIdentifier for `ecdsa-with-SHA256`. 100 | /// 101 | /// This is: 102 | /// 103 | /// ```text 104 | /// # ecdsa-with-SHA256 105 | /// OBJECT_IDENTIFIER { 1.2.840.10045.4.3.2 } 106 | /// ``` 107 | pub const ECDSA_SHA256: AlgorithmIdentifier = 108 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ecdsa-sha256.der")); 109 | 110 | /// AlgorithmIdentifier for `ecdsa-with-SHA384`. 111 | /// 112 | /// This is: 113 | /// 114 | /// ```text 115 | /// # ecdsa-with-SHA384 116 | /// OBJECT_IDENTIFIER { 1.2.840.10045.4.3.3 } 117 | /// ``` 118 | pub const ECDSA_SHA384: AlgorithmIdentifier = 119 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ecdsa-sha384.der")); 120 | 121 | /// AlgorithmIdentifier for `ecdsa-with-SHA512`. 122 | /// 123 | /// This is: 124 | /// 125 | /// ```text 126 | /// # ecdsa-with-SHA512 127 | /// OBJECT_IDENTIFIER { 1.2.840.10045.4.3.4 } 128 | /// ``` 129 | pub const ECDSA_SHA512: AlgorithmIdentifier = 130 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ecdsa-sha512.der")); 131 | 132 | /// AlgorithmIdentifier for `rsaEncryption`. 133 | /// 134 | /// This is: 135 | /// 136 | /// ```text 137 | /// # rsaEncryption 138 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.1 } 139 | /// NULL {} 140 | /// ``` 141 | pub const RSA_ENCRYPTION: AlgorithmIdentifier = 142 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-rsa-encryption.der")); 143 | 144 | /// AlgorithmIdentifier for `sha256WithRSAEncryption`. 145 | /// 146 | /// This is: 147 | /// 148 | /// ```text 149 | /// # sha256WithRSAEncryption 150 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.11 } 151 | /// NULL {} 152 | /// ``` 153 | pub const RSA_PKCS1_SHA256: AlgorithmIdentifier = 154 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-rsa-pkcs1-sha256.der")); 155 | 156 | /// AlgorithmIdentifier for `sha384WithRSAEncryption`. 157 | /// 158 | /// This is: 159 | /// 160 | /// ```text 161 | /// # sha384WithRSAEncryption 162 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.12 } 163 | /// NULL {} 164 | /// ``` 165 | pub const RSA_PKCS1_SHA384: AlgorithmIdentifier = 166 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-rsa-pkcs1-sha384.der")); 167 | 168 | /// AlgorithmIdentifier for `sha512WithRSAEncryption`. 169 | /// 170 | /// This is: 171 | /// 172 | /// ```text 173 | /// # sha512WithRSAEncryption 174 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.13 } 175 | /// NULL {} 176 | /// ``` 177 | pub const RSA_PKCS1_SHA512: AlgorithmIdentifier = 178 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-rsa-pkcs1-sha512.der")); 179 | 180 | /// AlgorithmIdentifier for `rsassaPss` with: 181 | /// 182 | /// - hashAlgorithm: sha256 183 | /// - maskGenAlgorithm: mgf1 with sha256 184 | /// - saltLength: 32 185 | /// 186 | /// This is: 187 | /// 188 | /// ```text 189 | /// # rsassa-pss 190 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } 191 | /// SEQUENCE { 192 | /// # hashAlgorithm: 193 | /// [0] { 194 | /// SEQUENCE { 195 | /// # sha256 196 | /// OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.1 } 197 | /// NULL {} 198 | /// } 199 | /// } 200 | /// # maskGenAlgorithm: 201 | /// [1] { 202 | /// SEQUENCE { 203 | /// # mgf1 204 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } 205 | /// SEQUENCE { 206 | /// # sha256 207 | /// OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.1 } 208 | /// NULL {} 209 | /// } 210 | /// } 211 | /// } 212 | /// # saltLength: 213 | /// [2] { 214 | /// INTEGER { 32 } 215 | /// } 216 | /// } 217 | /// ``` 218 | /// 219 | /// See for 220 | /// the meaning of the context-specific tags. 221 | pub const RSA_PSS_SHA256: AlgorithmIdentifier = 222 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-rsa-pss-sha256.der")); 223 | 224 | /// AlgorithmIdentifier for `rsassaPss` with: 225 | /// 226 | /// - hashAlgorithm: sha384 227 | /// - maskGenAlgorithm: mgf1 with sha384 228 | /// - saltLength: 48 229 | /// 230 | /// This is: 231 | /// 232 | /// ```text 233 | /// # rsassa-pss 234 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } 235 | /// SEQUENCE { 236 | /// # hashAlgorithm: 237 | /// [0] { 238 | /// SEQUENCE { 239 | /// # sha384 240 | /// OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.2 } 241 | /// NULL {} 242 | /// } 243 | /// } 244 | /// # maskGenAlgorithm: 245 | /// [1] { 246 | /// SEQUENCE { 247 | /// # mgf1 248 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } 249 | /// SEQUENCE { 250 | /// # sha384 251 | /// OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.2 } 252 | /// NULL {} 253 | /// } 254 | /// } 255 | /// } 256 | /// # saltLength: 257 | /// [2] { 258 | /// INTEGER { 48 } 259 | /// } 260 | /// } 261 | /// ``` 262 | /// 263 | /// See for 264 | /// the meaning of the context-specific tags. 265 | pub const RSA_PSS_SHA384: AlgorithmIdentifier = 266 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-rsa-pss-sha384.der")); 267 | 268 | /// AlgorithmIdentifier for `rsassaPss` with: 269 | /// 270 | /// - hashAlgorithm: sha512 271 | /// - maskGenAlgorithm: mgf1 with sha512 272 | /// - saltLength: 64 273 | /// 274 | /// This is: 275 | /// 276 | /// ```text 277 | /// # rsassa-pss 278 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.10 } 279 | /// SEQUENCE { 280 | /// # hashAlgorithm: 281 | /// [0] { 282 | /// SEQUENCE { 283 | /// # sha512 284 | /// OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.3 } 285 | /// NULL {} 286 | /// } 287 | /// } 288 | /// # maskGenAlgorithm: 289 | /// [1] { 290 | /// SEQUENCE { 291 | /// # mgf1 292 | /// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.8 } 293 | /// SEQUENCE { 294 | /// # sha512 295 | /// OBJECT_IDENTIFIER { 2.16.840.1.101.3.4.2.3 } 296 | /// NULL {} 297 | /// } 298 | /// } 299 | /// } 300 | /// # saltLength: 301 | /// [2] { 302 | /// INTEGER { 64 } 303 | /// } 304 | /// } 305 | /// ``` 306 | /// 307 | /// See for 308 | /// the meaning of the context-specific tags. 309 | pub const RSA_PSS_SHA512: AlgorithmIdentifier = 310 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-rsa-pss-sha512.der")); 311 | 312 | /// AlgorithmIdentifier for `ED25519`. 313 | /// 314 | /// This is: 315 | /// 316 | /// ```text 317 | /// # ed25519 318 | /// OBJECT_IDENTIFIER { 1.3.101.112 } 319 | /// ``` 320 | pub const ED25519: AlgorithmIdentifier = 321 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ed25519.der")); 322 | 323 | /// AlgorithmIdentifier for `ED448`. 324 | /// 325 | /// This is: 326 | /// 327 | /// ```text 328 | /// # ed448 329 | /// OBJECT_IDENTIFIER { 1.3.101.113 } 330 | /// ``` 331 | pub const ED448: AlgorithmIdentifier = 332 | AlgorithmIdentifier::from_slice(include_bytes!("data/alg-ed448.der")); 333 | 334 | /// A DER encoding of the PKIX AlgorithmIdentifier type: 335 | /// 336 | /// ```ASN.1 337 | /// AlgorithmIdentifier ::= SEQUENCE { 338 | /// algorithm OBJECT IDENTIFIER, 339 | /// parameters ANY DEFINED BY algorithm OPTIONAL } 340 | /// -- contains a value of the type 341 | /// -- registered for use with the 342 | /// -- algorithm object identifier value 343 | /// ``` 344 | /// (from ) 345 | /// 346 | /// The outer sequence encoding is *not included*, so this is the DER encoding 347 | /// of an OID for `algorithm` plus the `parameters` value. 348 | /// 349 | /// For example, this is the `rsaEncryption` algorithm (but prefer to use the constant 350 | /// [`RSA_ENCRYPTION`] instead): 351 | /// 352 | /// ``` 353 | /// let rsa_encryption = rustls_pki_types::AlgorithmIdentifier::from_slice( 354 | /// &[ 355 | /// // algorithm: 1.2.840.113549.1.1.1 356 | /// 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 357 | /// // parameters: NULL 358 | /// 0x05, 0x00 359 | /// ] 360 | /// ); 361 | /// assert_eq!(rustls_pki_types::alg_id::RSA_ENCRYPTION, rsa_encryption); 362 | /// ``` 363 | /// 364 | /// Common values for this type are provided in this module. 365 | #[derive(Clone, Copy, PartialEq, Eq)] 366 | pub struct AlgorithmIdentifier(&'static [u8]); 367 | 368 | impl AlgorithmIdentifier { 369 | /// Makes a new `AlgorithmIdentifier` from a static octet slice. 370 | /// 371 | /// This does not validate the contents of the slice. 372 | pub const fn from_slice(bytes: &'static [u8]) -> Self { 373 | Self(bytes) 374 | } 375 | } 376 | 377 | impl AsRef<[u8]> for AlgorithmIdentifier { 378 | fn as_ref(&self) -> &[u8] { 379 | self.0 380 | } 381 | } 382 | 383 | impl fmt::Debug for AlgorithmIdentifier { 384 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 385 | super::hex(f, self.0) 386 | } 387 | } 388 | 389 | impl Deref for AlgorithmIdentifier { 390 | type Target = [u8]; 391 | 392 | fn deref(&self) -> &Self::Target { 393 | self.as_ref() 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /tests/dns_name.rs: -------------------------------------------------------------------------------- 1 | use rustls_pki_types::DnsName; 2 | 3 | // (name, is_valid) 4 | static DNS_NAME_VALIDITY: &[(&[u8], bool)] = &[ 5 | (b"a", true), 6 | (b"a.b", true), 7 | (b"a.b.c", true), 8 | (b"a.b.c.d", true), 9 | 10 | // Hyphens, one component. 11 | (b"-", false), 12 | (b"-a", false), 13 | (b"a-", false), 14 | (b"a-b", true), 15 | 16 | // Hyphens, last component. 17 | (b"a.-", false), 18 | (b"a.-a", false), 19 | (b"a.a-", false), 20 | (b"a.a-b", true), 21 | 22 | // Hyphens, not last component. 23 | (b"-.a", false), 24 | (b"-a.a", false), 25 | (b"a-.a", false), 26 | (b"a-b.a", true), 27 | 28 | // Underscores, one component. 29 | (b"_", true), // TODO: Perhaps this should be rejected for '_' being sole character?. 30 | (b"_a", true), // TODO: Perhaps this should be rejected for '_' being 1st? 31 | (b"a_", true), 32 | (b"a_b", true), 33 | 34 | // Underscores, last component. 35 | (b"a._", true), // TODO: Perhaps this should be rejected for '_' being sole character?. 36 | (b"a._a", true), // TODO: Perhaps this should be rejected for '_' being 1st? 37 | (b"a.a_", true), 38 | (b"a.a_b", true), 39 | 40 | // Underscores, not last component. 41 | (b"_.a", true), // TODO: Perhaps this should be rejected for '_' being sole character?. 42 | (b"_a.a", true), 43 | (b"a_.a", true), 44 | (b"a_b.a", true), 45 | 46 | // empty labels 47 | (b"", false), 48 | (b".", false), 49 | (b"a", true), 50 | (b".a", false), 51 | (b".a.b", false), 52 | (b"..a", false), 53 | (b"a..b", false), 54 | (b"a...b", false), 55 | (b"a..b.c", false), 56 | (b"a.b..c", false), 57 | (b".a.b.c.", false), 58 | 59 | // absolute names 60 | (b"a.", true), 61 | (b"a.b.", true), 62 | (b"a.b.c.", true), 63 | 64 | // absolute names with empty label at end 65 | (b"a..", false), 66 | (b"a.b..", false), 67 | (b"a.b.c..", false), 68 | (b"a...", false), 69 | 70 | // Punycode 71 | (b"xn--", false), 72 | (b"xn--.", false), 73 | (b"xn--.a", false), 74 | (b"a.xn--", false), 75 | (b"a.xn--.", false), 76 | (b"a.xn--.b", false), 77 | (b"a.xn--.b", false), 78 | (b"a.xn--\0.b", false), 79 | (b"a.xn--a.b", true), 80 | (b"xn--a", true), 81 | (b"a.xn--a", true), 82 | (b"a.xn--a.a", true), 83 | (b"\xc4\x95.com", false), // UTF-8 ĕ 84 | (b"xn--jea.com", true), // punycode ĕ 85 | (b"xn--\xc4\x95.com", false), // UTF-8 ĕ, malformed punycode + UTF-8 mashup 86 | 87 | // Surprising punycode 88 | (b"xn--google.com", true), // 䕮䕵䕶䕱.com 89 | (b"xn--citibank.com", true), // 岍岊岊岅岉岎.com 90 | (b"xn--cnn.com", true), // 䁾.com 91 | (b"a.xn--cnn", true), // a.䁾 92 | (b"a.xn--cnn.com", true), // a.䁾.com 93 | 94 | (b"1.2.3.4", false), // IPv4 address 95 | (b"1::2", false), // IPV6 address 96 | 97 | // whitespace not allowed anywhere. 98 | (b" ", false), 99 | (b" a", false), 100 | (b"a ", false), 101 | (b"a b", false), 102 | (b"a.b 1", false), 103 | (b"a\t", false), 104 | 105 | // Nulls not allowed 106 | (b"\0", false), 107 | (b"a\0", false), 108 | (b"example.org\0.example.com", false), // Hi Moxie! 109 | (b"\0a", false), 110 | (b"xn--\0", false), 111 | 112 | // Allowed character set 113 | (b"a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z", true), 114 | (b"A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z", true), 115 | (b"0.1.2.3.4.5.6.7.8.9.a", true), // "a" needed to avoid numeric last label 116 | (b"a-b", true), // hyphen (a label cannot start or end with a hyphen) 117 | 118 | // An invalid character in various positions 119 | (b"!", false), 120 | (b"!a", false), 121 | (b"a!", false), 122 | (b"a!b", false), 123 | (b"a.!", false), 124 | (b"a.a!", false), 125 | (b"a.!a", false), 126 | (b"a.a!a", false), 127 | (b"a.!a.a", false), 128 | (b"a.a!.a", false), 129 | (b"a.a!a.a", false), 130 | 131 | // Various other invalid characters 132 | (b"a!", false), 133 | (b"a@", false), 134 | (b"a#", false), 135 | (b"a$", false), 136 | (b"a%", false), 137 | (b"a^", false), 138 | (b"a&", false), 139 | (b"a*", false), 140 | (b"a(", false), 141 | (b"a)", false), 142 | 143 | // last label can't be fully numeric 144 | (b"1", false), 145 | (b"a.1", false), 146 | 147 | // other labels can be fully numeric 148 | (b"1.a", true), 149 | (b"1.2.a", true), 150 | (b"1.2.3.a", true), 151 | 152 | // last label can be *partly* numeric 153 | (b"1a", true), 154 | (b"1.1a", true), 155 | (b"1-1", true), 156 | (b"a.1-1", true), 157 | (b"a.1-a", true), 158 | 159 | // labels cannot start with a hyphen 160 | (b"-", false), 161 | (b"-1", false), 162 | 163 | // labels cannot end with a hyphen 164 | (b"1-", false), 165 | (b"1-.a", false), 166 | (b"a-", false), 167 | (b"a-.a", false), 168 | (b"a.1-.a", false), 169 | (b"a.a-.a", false), 170 | 171 | // labels can contain a hyphen in the middle 172 | (b"a-b", true), 173 | (b"1-2", true), 174 | (b"a.a-1", true), 175 | 176 | // multiple consecutive hyphens allowed 177 | (b"a--1", true), 178 | (b"1---a", true), 179 | (b"a-----------------b", true), 180 | 181 | // Wildcard specifications are not valid reference names. 182 | (b"*.a", false), 183 | (b"a*", false), 184 | (b"a*.", false), 185 | (b"a*.a", false), 186 | (b"a*.a.", false), 187 | (b"*.a.b", false), 188 | (b"*.a.b.", false), 189 | (b"a*.b.c", false), 190 | (b"*.a.b.c", false), 191 | (b"a*.b.c.d", false), 192 | 193 | // Multiple wildcards. 194 | (b"a**.b.c", false), 195 | (b"a*b*.c.d", false), 196 | (b"a*.b*.c", false), 197 | 198 | // Wildcards not in the first label. 199 | (b"a.*", false), 200 | (b"a.*.b", false), 201 | (b"a.b.*", false), 202 | (b"a.b*.c", false), 203 | (b"*.b*.c", false), 204 | (b".*.a.b", false), 205 | (b".a*.b.c", false), 206 | 207 | // Wildcards not at the end of the first label. 208 | (b"*a.b.c", false), 209 | (b"a*b.c.d", false), 210 | 211 | // Wildcards and IDNA prefix. 212 | (b"x*.a.b", false), 213 | (b"xn*.a.b", false), 214 | (b"xn-*.a.b", false), 215 | (b"xn--*.a.b", false), 216 | (b"xn--w*.a.b", false), 217 | 218 | // Redacted labels from RFC6962bis draft 4 219 | // https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-04#section-3.2.2 220 | (b"(PRIVATE).foo", false), 221 | 222 | // maximum label length is 63 characters 223 | (b"123456789012345678901234567890123456789012345678901234567890abc", true), 224 | (b"123456789012345678901234567890123456789012345678901234567890abcd", false), 225 | 226 | // maximum total length is 253 characters 227 | (b"12345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890.123456789012345678901234567890123456789012345678a", 228 | true), 229 | (b"12345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890.12345678901234567890123456789012345678901234567890.1234567890123456789012345678901234567890123456789a", 230 | false), 231 | ]; 232 | 233 | // (IP address, is valid DNS name). The comments here refer to the validity of 234 | // the string as an IP address, not as a DNS name validity. 235 | static IP_ADDRESS_DNS_VALIDITY: &[(&[u8], bool)] = &[ 236 | (b"", false), 237 | (b"1", false), 238 | (b"1.2", false), 239 | (b"1.2.3", false), 240 | (b"1.2.3.4", false), 241 | (b"1.2.3.4.5", false), 242 | (b"1.2.3.4a", true), // a DNS name! 243 | (b"a.2.3.4", false), // not even a DNS name! 244 | (b"1::2", false), // IPv6 address 245 | // Whitespace not allowed 246 | (b" 1.2.3.4", false), 247 | (b"1.2.3.4 ", false), 248 | (b"1 .2.3.4", false), 249 | (b"\n1.2.3.4", false), 250 | (b"1.2.3.4\n", false), 251 | // Nulls not allowed 252 | (b"\x00", false), 253 | (b"\x001.2.3.4", false), 254 | (b"1.2.3.4\x00", false), 255 | (b"1.2.3.4\x00.5", false), 256 | // Range 257 | (b"0.0.0.0", false), 258 | (b"255.255.255.255", false), 259 | (b"256.0.0.0", false), 260 | (b"0.256.0.0", false), 261 | (b"0.0.256.0", false), 262 | (b"0.0.0.256", false), 263 | (b"999.0.0.0", false), 264 | (b"9999999999999999999.0.0.0", false), 265 | // All digits allowed 266 | (b"0.1.2.3", false), 267 | (b"4.5.6.7", false), 268 | (b"8.9.0.1", false), 269 | // Leading zeros not allowed 270 | (b"01.2.3.4", false), 271 | (b"001.2.3.4", false), 272 | (b"00000000001.2.3.4", false), 273 | (b"010.2.3.4", false), 274 | (b"1.02.3.4", false), 275 | (b"1.2.03.4", false), 276 | (b"1.2.3.04", false), 277 | // Empty components 278 | (b".2.3.4", false), 279 | (b"1..3.4", false), 280 | (b"1.2..4", false), 281 | (b"1.2.3.", false), 282 | // Too many components 283 | (b"1.2.3.4.5", false), 284 | (b"1.2.3.4.5.6", false), 285 | (b"0.1.2.3.4", false), 286 | (b"1.2.3.4.0", false), 287 | // Leading/trailing dot 288 | (b".1.2.3.4", false), 289 | (b"1.2.3.4.", false), 290 | // Other common forms of IPv4 address 291 | // http://en.wikipedia.org/wiki/IPv4#Address_representations 292 | (b"192.0.2.235", false), // dotted decimal (control value) 293 | (b"0xC0.0x00.0x02.0xEB", true), // dotted hex - actually a DNS name! 294 | (b"0301.0000.0002.0353", false), // dotted octal 295 | (b"0xC00002EB", true), // non-dotted hex, actually a DNS name! 296 | (b"3221226219", false), // non-dotted decimal 297 | (b"030000001353", false), // non-dotted octal 298 | (b"192.0.0002.0xEB", true), // mixed, actually a DNS name! 299 | (b"1234", false), 300 | (b"1234:5678", false), 301 | (b"1234:5678:9abc", false), 302 | (b"1234:5678:9abc:def0", false), 303 | (b"1234:5678:9abc:def0:1234:", false), 304 | (b"1234:5678:9abc:def0:1234:5678:", false), 305 | (b"1234:5678:9abc:def0:1234:5678:9abc:", false), 306 | (b"1234:5678:9abc:def0:1234:5678:9abc:def0", false), 307 | (b"1234:5678:9abc:def0:1234:5678:9abc:def0:", false), 308 | (b":1234:5678:9abc:def0:1234:5678:9abc:def0", false), 309 | (b"1234:5678:9abc:def0:1234:5678:9abc:def0:0000", false), 310 | // Valid contractions 311 | (b"::1", false), 312 | (b"::1234", false), 313 | (b"1234::", false), 314 | (b"1234::5678", false), 315 | (b"1234:5678::abcd", false), 316 | (b"1234:5678:9abc:def0:1234:5678:9abc::", false), 317 | // Contraction in full IPv6 addresses not allowed 318 | (b"::1234:5678:9abc:def0:1234:5678:9abc:def0", false), // start 319 | (b"1234:5678:9abc:def0:1234:5678:9abc:def0::", false), // end 320 | (b"1234:5678::9abc:def0:1234:5678:9abc:def0", false), // interior 321 | // Multiple contractions not allowed 322 | (b"::1::", false), 323 | (b"::1::2", false), 324 | (b"1::2::", false), 325 | // Colon madness! 326 | (b":", false), 327 | (b"::", false), 328 | (b":::", false), 329 | (b"::::", false), 330 | (b":::1", false), 331 | (b"::::1", false), 332 | (b"1:::2", false), 333 | (b"1::::2", false), 334 | (b"1:2:::", false), 335 | (b"1:2::::", false), 336 | (b"::1234:", false), 337 | (b":1234::", false), 338 | (b"01234::", false), // too many digits, even if zero 339 | (b"12345678::", false), // too many digits or missing colon 340 | // uppercase 341 | (b"ABCD:EFAB::", false), 342 | // miXeD CAse 343 | (b"aBcd:eFAb::", false), 344 | // IPv4-style 345 | (b"::2.3.4.5", false), 346 | (b"1234::2.3.4.5", false), 347 | (b"::abcd:2.3.4.5", false), 348 | (b"1234:5678:9abc:def0:1234:5678:252.253.254.255", false), 349 | (b"1234:5678:9abc:def0:1234::252.253.254.255", false), 350 | (b"1234::252.253.254", false), 351 | (b"::252.253.254", false), 352 | (b"::252.253.254.300", false), 353 | (b"1234::252.253.254.255:", false), 354 | (b"1234::252.253.254.255:5678", false), 355 | // Contractions that don't contract 356 | (b"::1234:5678:9abc:def0:1234:5678:9abc:def0", false), 357 | (b"1234:5678:9abc:def0:1234:5678:9abc:def0::", false), 358 | (b"1234:5678:9abc:def0::1234:5678:9abc:def0", false), 359 | (b"1234:5678:9abc:def0:1234:5678::252.253.254.255", false), 360 | // With and without leading zeros 361 | (b"::123", false), 362 | (b"::0123", false), 363 | (b"::012", false), 364 | (b"::0012", false), 365 | (b"::01", false), 366 | (b"::001", false), 367 | (b"::0001", false), 368 | (b"::0", false), 369 | (b"::00", false), 370 | (b"::000", false), 371 | (b"::0000", false), 372 | (b"::01234", false), 373 | (b"::00123", false), 374 | (b"::000123", false), 375 | // Trailing zero 376 | (b"::12340", false), 377 | // Whitespace 378 | (b" 1234:5678:9abc:def0:1234:5678:9abc:def0", false), 379 | (b"\t1234:5678:9abc:def0:1234:5678:9abc:def0", false), 380 | (b"\t1234:5678:9abc:def0:1234:5678:9abc:def0\n", false), 381 | (b"1234 :5678:9abc:def0:1234:5678:9abc:def0", false), 382 | (b"1234: 5678:9abc:def0:1234:5678:9abc:def0", false), 383 | (b":: 2.3.4.5", false), 384 | (b"1234::252.253.254.255 ", false), 385 | (b"1234::252.253.254.255\n", false), 386 | (b"1234::252.253. 254.255", false), 387 | // Nulls 388 | (b"\x00", false), 389 | (b"::1\x00:2", false), 390 | (b"::1\x00", false), 391 | (b"::1.2.3.4\x00", false), 392 | (b"::1.2\x002.3.4", false), 393 | ]; 394 | 395 | #[test] 396 | fn dns_name_ref_try_from_ascii_test() { 397 | for &(s, is_valid) in DNS_NAME_VALIDITY 398 | .iter() 399 | .chain(IP_ADDRESS_DNS_VALIDITY.iter()) 400 | { 401 | assert_eq!( 402 | DnsName::try_from(s).is_ok(), 403 | is_valid, 404 | "DnsNameRef::try_from_ascii_str failed for \"{s:?}\"" 405 | ); 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/pem.rs: -------------------------------------------------------------------------------- 1 | use alloc::format; 2 | use alloc::string::String; 3 | use alloc::vec; 4 | use alloc::vec::Vec; 5 | use core::fmt; 6 | use core::marker::PhantomData; 7 | use core::ops::ControlFlow; 8 | #[cfg(feature = "std")] 9 | use std::fs::File; 10 | #[cfg(feature = "std")] 11 | use std::io::{self, ErrorKind}; 12 | 13 | use crate::base64; 14 | 15 | /// Items that can be decoded from PEM data. 16 | pub trait PemObject: Sized { 17 | /// Decode the first section of this type from PEM contained in 18 | /// a byte slice. 19 | /// 20 | /// [`Error::NoItemsFound`] is returned if no such items are found. 21 | fn from_pem_slice(pem: &[u8]) -> Result { 22 | Self::pem_slice_iter(pem) 23 | .next() 24 | .unwrap_or(Err(Error::NoItemsFound)) 25 | } 26 | 27 | /// Iterate over all sections of this type from PEM contained in 28 | /// a byte slice. 29 | fn pem_slice_iter(pem: &[u8]) -> SliceIter<'_, Self> { 30 | SliceIter::new(pem) 31 | } 32 | 33 | /// Decode the first section of this type from the PEM contents of the named file. 34 | /// 35 | /// [`Error::NoItemsFound`] is returned if no such items are found. 36 | #[cfg(feature = "std")] 37 | fn from_pem_file(file_name: impl AsRef) -> Result { 38 | Self::pem_file_iter(file_name)? 39 | .next() 40 | .unwrap_or(Err(Error::NoItemsFound)) 41 | } 42 | 43 | /// Iterate over all sections of this type from the PEM contents of the named file. 44 | /// 45 | /// This reports errors in two phases: 46 | /// 47 | /// - errors opening the file are reported from this function directly, 48 | /// - errors reading from the file are reported from the returned iterator, 49 | #[cfg(feature = "std")] 50 | fn pem_file_iter( 51 | file_name: impl AsRef, 52 | ) -> Result, Self>, Error> { 53 | Ok(ReadIter::new(io::BufReader::new( 54 | File::open(file_name).map_err(Error::Io)?, 55 | ))) 56 | } 57 | 58 | /// Decode the first section of this type from PEM read from an [`io::Read`]. 59 | #[cfg(feature = "std")] 60 | fn from_pem_reader(rd: impl std::io::Read) -> Result { 61 | Self::pem_reader_iter(rd) 62 | .next() 63 | .unwrap_or(Err(Error::NoItemsFound)) 64 | } 65 | 66 | /// Iterate over all sections of this type from PEM present in an [`io::Read`]. 67 | #[cfg(feature = "std")] 68 | fn pem_reader_iter(rd: R) -> ReadIter, Self> { 69 | ReadIter::new(io::BufReader::new(rd)) 70 | } 71 | 72 | /// Conversion from a PEM [`SectionKind`] and body data. 73 | /// 74 | /// This inspects `kind`, and if it matches this type's PEM section kind, 75 | /// converts `der` into this type. 76 | fn from_pem(kind: SectionKind, der: Vec) -> Option; 77 | } 78 | 79 | pub(crate) trait PemObjectFilter: PemObject + From> { 80 | const KIND: SectionKind; 81 | } 82 | 83 | impl>> PemObject for T { 84 | fn from_pem(kind: SectionKind, der: Vec) -> Option { 85 | match Self::KIND == kind { 86 | true => Some(Self::from(der)), 87 | false => None, 88 | } 89 | } 90 | } 91 | 92 | /// Extract and return all PEM sections by reading `rd`. 93 | #[cfg(feature = "std")] 94 | pub struct ReadIter { 95 | rd: R, 96 | _ty: PhantomData, 97 | line: Vec, 98 | b64_buf: Vec, 99 | } 100 | 101 | #[cfg(feature = "std")] 102 | impl ReadIter { 103 | /// Create a new iterator. 104 | pub fn new(rd: R) -> Self { 105 | Self { 106 | rd, 107 | _ty: PhantomData, 108 | line: Vec::with_capacity(80), 109 | b64_buf: Vec::with_capacity(1024), 110 | } 111 | } 112 | } 113 | 114 | #[cfg(feature = "std")] 115 | impl Iterator for ReadIter { 116 | type Item = Result; 117 | 118 | fn next(&mut self) -> Option { 119 | loop { 120 | self.b64_buf.clear(); 121 | return match from_buf_inner(&mut self.rd, &mut self.line, &mut self.b64_buf) { 122 | Ok(Some((sec, item))) => match T::from_pem(sec, item) { 123 | Some(res) => Some(Ok(res)), 124 | None => continue, 125 | }, 126 | Ok(None) => return None, 127 | Err(err) => Some(Err(err)), 128 | }; 129 | } 130 | } 131 | } 132 | 133 | /// Iterator over all PEM sections in a `&[u8]` slice. 134 | pub struct SliceIter<'a, T> { 135 | current: &'a [u8], 136 | _ty: PhantomData, 137 | b64_buf: Vec, 138 | } 139 | 140 | impl<'a, T: PemObject> SliceIter<'a, T> { 141 | /// Create a new iterator. 142 | pub fn new(current: &'a [u8]) -> Self { 143 | Self { 144 | current, 145 | _ty: PhantomData, 146 | b64_buf: Vec::with_capacity(1024), 147 | } 148 | } 149 | 150 | /// Extract and decode the next supported PEM section from `input` 151 | /// 152 | /// - `Ok(None)` is returned if there is no PEM section to read from `input` 153 | /// - Syntax errors and decoding errors produce a `Err(...)` 154 | /// - Otherwise each decoded section is returned with a `Ok(Some((..., remainder)))` where 155 | /// `remainder` is the part of the `input` that follows the returned section 156 | fn read_section(&mut self) -> Result)>, Error> { 157 | self.b64_buf.clear(); 158 | let mut section = None; 159 | loop { 160 | let next_line = if let Some(index) = self 161 | .current 162 | .iter() 163 | .position(|byte| *byte == b'\n' || *byte == b'\r') 164 | { 165 | let (line, newline_plus_remainder) = self.current.split_at(index); 166 | self.current = &newline_plus_remainder[1..]; 167 | Some(line) 168 | } else if !self.current.is_empty() { 169 | let next_line = self.current; 170 | self.current = &[]; 171 | Some(next_line) 172 | } else { 173 | None 174 | }; 175 | 176 | match read(next_line, &mut section, &mut self.b64_buf)? { 177 | ControlFlow::Continue(()) => continue, 178 | ControlFlow::Break(item) => return Ok(item), 179 | } 180 | } 181 | } 182 | 183 | /// Returns the rest of the unparsed data. 184 | /// 185 | /// This is the slice immediately following the most 186 | /// recently returned item from `next()`. 187 | #[doc(hidden)] 188 | pub fn remainder(&self) -> &'a [u8] { 189 | self.current 190 | } 191 | } 192 | 193 | impl Iterator for SliceIter<'_, T> { 194 | type Item = Result; 195 | 196 | fn next(&mut self) -> Option { 197 | loop { 198 | return match self.read_section() { 199 | Ok(Some((sec, item))) => match T::from_pem(sec, item) { 200 | Some(res) => Some(Ok(res)), 201 | None => continue, 202 | }, 203 | Ok(None) => return None, 204 | Err(err) => Some(Err(err)), 205 | }; 206 | } 207 | } 208 | } 209 | 210 | impl PemObject for (SectionKind, Vec) { 211 | fn from_pem(kind: SectionKind, der: Vec) -> Option { 212 | Some((kind, der)) 213 | } 214 | } 215 | 216 | /// Extract and decode the next supported PEM section from `rd`. 217 | /// 218 | /// - Ok(None) is returned if there is no PEM section read from `rd`. 219 | /// - Underlying IO errors produce a `Err(...)` 220 | /// - Otherwise each decoded section is returned with a `Ok(Some(...))` 221 | #[cfg(feature = "std")] 222 | pub fn from_buf(rd: &mut dyn io::BufRead) -> Result)>, Error> { 223 | let mut b64buf = Vec::with_capacity(1024); 224 | let mut line = Vec::with_capacity(80); 225 | from_buf_inner(rd, &mut line, &mut b64buf) 226 | } 227 | 228 | #[cfg(feature = "std")] 229 | fn from_buf_inner( 230 | rd: &mut dyn io::BufRead, 231 | line: &mut Vec, 232 | b64buf: &mut Vec, 233 | ) -> Result)>, Error> { 234 | let mut section = None; 235 | loop { 236 | line.clear(); 237 | let len = read_until_newline(rd, line).map_err(Error::Io)?; 238 | 239 | let next_line = if len == 0 { 240 | None 241 | } else { 242 | Some(line.as_slice()) 243 | }; 244 | 245 | match read(next_line, &mut section, b64buf) { 246 | Ok(ControlFlow::Break(opt)) => return Ok(opt), 247 | Ok(ControlFlow::Continue(())) => continue, 248 | Err(e) => return Err(e), 249 | } 250 | } 251 | } 252 | 253 | #[allow(clippy::type_complexity)] 254 | fn read( 255 | next_line: Option<&[u8]>, 256 | section: &mut Option, 257 | b64buf: &mut Vec, 258 | ) -> Result)>, ()>, Error> { 259 | let line = if let Some(line) = next_line { 260 | line 261 | } else { 262 | // EOF 263 | return match section.take() { 264 | Some(label) => Err(Error::MissingSectionEnd { 265 | end_marker: label.as_ref().to_vec(), 266 | }), 267 | None => Ok(ControlFlow::Break(None)), 268 | }; 269 | }; 270 | 271 | if line.starts_with(b"-----BEGIN ") { 272 | let (mut trailer, mut pos) = (0, line.len()); 273 | for (i, &b) in line.iter().enumerate().rev() { 274 | match b { 275 | b'-' => { 276 | trailer += 1; 277 | pos = i; 278 | } 279 | b'\n' | b'\r' | b' ' => continue, 280 | _ => break, 281 | } 282 | } 283 | 284 | if trailer != 5 { 285 | return Err(Error::IllegalSectionStart { 286 | line: line.to_vec(), 287 | }); 288 | } 289 | 290 | let ty = &line[11..pos]; 291 | *section = Some(SectionLabel::from(ty)); 292 | return Ok(ControlFlow::Continue(())); 293 | } 294 | 295 | if let Some(label) = section.as_ref() { 296 | if label.is_end(line) { 297 | let kind = match label { 298 | SectionLabel::Known(kind) => *kind, 299 | // unhandled section: have caller try again 300 | SectionLabel::Unknown(_) => { 301 | *section = None; 302 | b64buf.clear(); 303 | return Ok(ControlFlow::Continue(())); 304 | } 305 | }; 306 | 307 | let mut der = vec![0u8; base64::decoded_length(b64buf.len())]; 308 | let der_len = match kind.secret() { 309 | true => base64::decode_secret(b64buf, &mut der), 310 | false => base64::decode_public(b64buf, &mut der), 311 | } 312 | .map_err(|err| Error::Base64Decode(format!("{err:?}")))? 313 | .len(); 314 | 315 | der.truncate(der_len); 316 | 317 | return Ok(ControlFlow::Break(Some((kind, der)))); 318 | } 319 | } 320 | 321 | if section.is_some() { 322 | b64buf.extend(line); 323 | } 324 | 325 | Ok(ControlFlow::Continue(())) 326 | } 327 | 328 | enum SectionLabel { 329 | Known(SectionKind), 330 | Unknown(Vec), 331 | } 332 | 333 | impl SectionLabel { 334 | fn is_end(&self, line: &[u8]) -> bool { 335 | let rest = match line.strip_prefix(b"-----END ") { 336 | Some(rest) => rest, 337 | None => return false, 338 | }; 339 | 340 | let ty = match self { 341 | Self::Known(kind) => kind.as_slice(), 342 | Self::Unknown(ty) => ty, 343 | }; 344 | 345 | let rest = match rest.strip_prefix(ty) { 346 | Some(rest) => rest, 347 | None => return false, 348 | }; 349 | 350 | rest.starts_with(b"-----") 351 | } 352 | } 353 | 354 | impl From<&[u8]> for SectionLabel { 355 | fn from(value: &[u8]) -> Self { 356 | match SectionKind::try_from(value) { 357 | Ok(kind) => Self::Known(kind), 358 | Err(_) => Self::Unknown(value.to_vec()), 359 | } 360 | } 361 | } 362 | 363 | impl AsRef<[u8]> for SectionLabel { 364 | fn as_ref(&self) -> &[u8] { 365 | match self { 366 | Self::Known(kind) => kind.as_slice(), 367 | Self::Unknown(ty) => ty, 368 | } 369 | } 370 | } 371 | 372 | /// A single recognised section in a PEM file. 373 | #[non_exhaustive] 374 | #[derive(Clone, Copy, Debug, PartialEq)] 375 | pub enum SectionKind { 376 | /// A DER-encoded x509 certificate. 377 | /// 378 | /// Appears as "CERTIFICATE" in PEM files. 379 | Certificate, 380 | 381 | /// A DER-encoded Subject Public Key Info; as specified in RFC 7468. 382 | /// 383 | /// Appears as "PUBLIC KEY" in PEM files. 384 | PublicKey, 385 | 386 | /// A DER-encoded plaintext RSA private key; as specified in PKCS #1/RFC 3447 387 | /// 388 | /// Appears as "RSA PRIVATE KEY" in PEM files. 389 | RsaPrivateKey, 390 | 391 | /// A DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958 392 | /// 393 | /// Appears as "PRIVATE KEY" in PEM files. 394 | PrivateKey, 395 | 396 | /// A Sec1-encoded plaintext private key; as specified in RFC 5915 397 | /// 398 | /// Appears as "EC PRIVATE KEY" in PEM files. 399 | EcPrivateKey, 400 | 401 | /// A Certificate Revocation List; as specified in RFC 5280 402 | /// 403 | /// Appears as "X509 CRL" in PEM files. 404 | Crl, 405 | 406 | /// A Certificate Signing Request; as specified in RFC 2986 407 | /// 408 | /// Appears as "CERTIFICATE REQUEST" in PEM files. 409 | Csr, 410 | 411 | /// An EchConfigList structure, as specified in 412 | /// . 413 | /// 414 | /// Appears as "ECHCONFIG" in PEM files. 415 | EchConfigList, 416 | } 417 | 418 | impl SectionKind { 419 | const fn secret(&self) -> bool { 420 | match self { 421 | Self::RsaPrivateKey | Self::PrivateKey | Self::EcPrivateKey => true, 422 | Self::Certificate | Self::PublicKey | Self::Crl | Self::Csr | Self::EchConfigList => { 423 | false 424 | } 425 | } 426 | } 427 | 428 | fn as_slice(&self) -> &'static [u8] { 429 | match self { 430 | Self::Certificate => b"CERTIFICATE", 431 | Self::PublicKey => b"PUBLIC KEY", 432 | Self::RsaPrivateKey => b"RSA PRIVATE KEY", 433 | Self::PrivateKey => b"PRIVATE KEY", 434 | Self::EcPrivateKey => b"EC PRIVATE KEY", 435 | Self::Crl => b"X509 CRL", 436 | Self::Csr => b"CERTIFICATE REQUEST", 437 | Self::EchConfigList => b"ECHCONFIG", 438 | } 439 | } 440 | } 441 | 442 | impl TryFrom<&[u8]> for SectionKind { 443 | type Error = (); 444 | 445 | fn try_from(value: &[u8]) -> Result { 446 | Ok(match value { 447 | b"CERTIFICATE" => Self::Certificate, 448 | b"PUBLIC KEY" => Self::PublicKey, 449 | b"RSA PRIVATE KEY" => Self::RsaPrivateKey, 450 | b"PRIVATE KEY" => Self::PrivateKey, 451 | b"EC PRIVATE KEY" => Self::EcPrivateKey, 452 | b"X509 CRL" => Self::Crl, 453 | b"CERTIFICATE REQUEST" => Self::Csr, 454 | b"ECHCONFIG" => Self::EchConfigList, 455 | _ => return Err(()), 456 | }) 457 | } 458 | } 459 | 460 | /// Errors that may arise when parsing the contents of a PEM file 461 | #[non_exhaustive] 462 | #[derive(Debug)] 463 | pub enum Error { 464 | /// a section is missing its "END marker" line 465 | MissingSectionEnd { 466 | /// the expected "END marker" line that was not found 467 | end_marker: Vec, 468 | }, 469 | 470 | /// syntax error found in the line that starts a new section 471 | IllegalSectionStart { 472 | /// line that contains the syntax error 473 | line: Vec, 474 | }, 475 | 476 | /// base64 decode error 477 | Base64Decode(String), 478 | 479 | /// I/O errors, from APIs that accept `std::io` types. 480 | #[cfg(feature = "std")] 481 | Io(io::Error), 482 | 483 | /// No items found of desired type 484 | NoItemsFound, 485 | } 486 | 487 | impl fmt::Display for Error { 488 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 489 | match self { 490 | Self::MissingSectionEnd { end_marker } => { 491 | write!(f, "missing section end marker: {end_marker:?}") 492 | } 493 | Self::IllegalSectionStart { line } => { 494 | write!(f, "illegal section start: {line:?}") 495 | } 496 | Self::Base64Decode(e) => write!(f, "base64 decode error: {e}"), 497 | #[cfg(feature = "std")] 498 | Self::Io(e) => write!(f, "I/O error: {e}"), 499 | Self::NoItemsFound => write!(f, "no items found"), 500 | } 501 | } 502 | } 503 | 504 | #[cfg(feature = "std")] 505 | impl std::error::Error for Error {} 506 | 507 | // Ported from https://github.com/rust-lang/rust/blob/91cfcb021935853caa06698b759c293c09d1e96a/library/std/src/io/mod.rs#L1990 and 508 | // modified to look for our accepted newlines. 509 | #[cfg(feature = "std")] 510 | fn read_until_newline(r: &mut R, buf: &mut Vec) -> io::Result { 511 | let mut read = 0; 512 | loop { 513 | let (done, used) = { 514 | let available = match r.fill_buf() { 515 | Ok(n) => n, 516 | Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, 517 | Err(e) => return Err(e), 518 | }; 519 | match available 520 | .iter() 521 | .copied() 522 | .position(|b| b == b'\n' || b == b'\r') 523 | { 524 | Some(i) => { 525 | buf.extend_from_slice(&available[..=i]); 526 | (true, i + 1) 527 | } 528 | None => { 529 | buf.extend_from_slice(available); 530 | (false, available.len()) 531 | } 532 | } 533 | }; 534 | r.consume(used); 535 | read += used; 536 | if done || used == 0 { 537 | return Ok(read); 538 | } 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /src/base64.rs: -------------------------------------------------------------------------------- 1 | /// Decode base64 `input`, writing the result into `output`. 2 | /// 3 | /// `input` is treated as secret, so efforts are made to avoid 4 | /// leaking its value via side channels, such as timing, 5 | /// memory accesses, and execution trace. 6 | /// 7 | /// The following is deemed non-secret information: 8 | /// 9 | /// - Appearance of whitespace in `input` 10 | /// - Erroneous characters in `input` (indeed, the first illegal 11 | /// character is quoted in the error type) 12 | /// - The length of `input` 13 | /// - The length of `output` 14 | /// 15 | /// Returns the prefix of `output` that was written to. 16 | pub(crate) fn decode_secret<'a>(input: &[u8], output: &'a mut [u8]) -> Result<&'a [u8], Error> { 17 | decode(input, output, CodePoint::decode_secret) 18 | } 19 | 20 | /// Decode base64 `input`, writing the result into `output`. 21 | /// 22 | /// `input` is treated as public information, so its value may 23 | /// be leaked via side channels. 24 | /// 25 | /// Returns the prefix of `output` that was written to. 26 | pub(crate) fn decode_public<'a>(input: &[u8], output: &'a mut [u8]) -> Result<&'a [u8], Error> { 27 | decode(input, output, CodePoint::decode_public) 28 | } 29 | 30 | /// Provide an upper limit on how much space could be required 31 | /// to decode a base64 encoding of len `base64_len`. 32 | pub(crate) const fn decoded_length(base64_len: usize) -> usize { 33 | ((base64_len + 3) / 4) * 3 34 | } 35 | 36 | fn decode<'a>( 37 | input: &[u8], 38 | output: &'a mut [u8], 39 | decode_byte: impl Fn(u8) -> CodePoint, 40 | ) -> Result<&'a [u8], Error> { 41 | let mut buffer = 0u64; 42 | let mut used = 0; 43 | let mut shift = SHIFT_INITIAL; 44 | let mut pad_mask = 0; 45 | 46 | let mut output_offset = 0; 47 | 48 | const SHIFT_INITIAL: i32 = (8 - 1) * 6; 49 | 50 | for byte in input.iter().copied() { 51 | let (item, pad) = match decode_byte(byte) { 52 | CodePoint::WHITESPACE => continue, 53 | CodePoint::INVALID => return Err(Error::InvalidCharacter(byte)), 54 | CodePoint::PAD => (0, 1), 55 | CodePoint(n) => (n, 0), 56 | }; 57 | 58 | // we collect 8 code points (therefore: 6 output bytes) into 59 | // `buffer`. this keeps this loop as tight as possible. 60 | if used == 8 { 61 | if pad_mask != 0b0000_0000 { 62 | return Err(Error::PrematurePadding); 63 | } 64 | 65 | let chunk = output 66 | .get_mut(output_offset..output_offset + 6) 67 | .ok_or(Error::InsufficientOutputSpace)?; 68 | 69 | chunk[0] = (buffer >> 40) as u8; 70 | chunk[1] = (buffer >> 32) as u8; 71 | chunk[2] = (buffer >> 24) as u8; 72 | chunk[3] = (buffer >> 16) as u8; 73 | chunk[4] = (buffer >> 8) as u8; 74 | chunk[5] = buffer as u8; 75 | 76 | output_offset += 6; 77 | buffer = 0; 78 | used = 0; 79 | pad_mask = 0; 80 | shift = SHIFT_INITIAL; 81 | } 82 | 83 | buffer |= (item as u64) << shift; 84 | shift -= 6; 85 | pad_mask |= pad << used; 86 | used += 1; 87 | } 88 | 89 | // reduce to final block 90 | if used > 4 { 91 | if pad_mask & 0b0000_1111 != 0 { 92 | return Err(Error::PrematurePadding); 93 | } 94 | let chunk = output 95 | .get_mut(output_offset..output_offset + 3) 96 | .ok_or(Error::InsufficientOutputSpace)?; 97 | chunk[0] = (buffer >> 40) as u8; 98 | chunk[1] = (buffer >> 32) as u8; 99 | chunk[2] = (buffer >> 24) as u8; 100 | 101 | buffer <<= 24; 102 | pad_mask >>= 4; 103 | used -= 4; 104 | output_offset += 3; 105 | } 106 | 107 | match (used, pad_mask) { 108 | // no trailing bytes 109 | (0, 0b0000) => {} 110 | 111 | // 4 trailing bytes, no padding 112 | (4, 0b0000) => { 113 | let chunk = output 114 | .get_mut(output_offset..output_offset + 3) 115 | .ok_or(Error::InsufficientOutputSpace)?; 116 | chunk[0] = (buffer >> 40) as u8; 117 | chunk[1] = (buffer >> 32) as u8; 118 | chunk[2] = (buffer >> 24) as u8; 119 | output_offset += 3; 120 | } 121 | 122 | // 4 trailing bytes with one padding char, or 3 trailing bytes 123 | (4, 0b1000) | (3, 0b0000) => { 124 | let chunk = output 125 | .get_mut(output_offset..output_offset + 2) 126 | .ok_or(Error::InsufficientOutputSpace)?; 127 | 128 | chunk[0] = (buffer >> 40) as u8; 129 | chunk[1] = (buffer >> 32) as u8; 130 | output_offset += 2; 131 | } 132 | 133 | // 4 trailing bytes with two padding char, or 2 trailing bytes 134 | (4, 0b1100) | (2, 0b0000) => { 135 | let chunk = output 136 | .get_mut(output_offset..output_offset + 1) 137 | .ok_or(Error::InsufficientOutputSpace)?; 138 | chunk[0] = (buffer >> 40) as u8; 139 | output_offset += 1; 140 | } 141 | 142 | // everything else is illegal 143 | _ => return Err(Error::InvalidTrailingPadding), 144 | } 145 | 146 | Ok(&output[..output_offset]) 147 | } 148 | 149 | #[derive(Debug, PartialEq)] 150 | pub(crate) enum Error { 151 | /// Given character is not valid in base64 alphabet. 152 | InvalidCharacter(u8), 153 | 154 | /// A padding character (`=`) appeared outside the final 155 | /// block of 4 characters. 156 | PrematurePadding, 157 | 158 | /// The padding characters at the end of the input were invalid. 159 | InvalidTrailingPadding, 160 | 161 | /// Not enough space in output buffer. 162 | /// 163 | /// Use `decoded_length` to get an upper bound. 164 | InsufficientOutputSpace, 165 | } 166 | 167 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 168 | struct CodePoint(u8); 169 | 170 | impl CodePoint { 171 | const WHITESPACE: Self = Self(0xf0); 172 | const PAD: Self = Self(0xf1); 173 | const INVALID: Self = Self(0xf2); 174 | } 175 | 176 | impl CodePoint { 177 | /// Side-channel rules: 178 | /// 179 | /// - code paths that produce `CodePoint(n)` must not make 180 | /// `n` observable via a side channel. 181 | /// - other code paths -- whitespace, padding or invalid -- need not, 182 | /// these are not considered secret conditions. 183 | fn decode_secret(b: u8) -> Self { 184 | let is_upper = u8_in_range(b, b'A', b'Z'); 185 | let is_lower = u8_in_range(b, b'a', b'z'); 186 | let is_digit = u8_in_range(b, b'0', b'9'); 187 | let is_plus = u8_equals(b, b'+'); 188 | let is_slash = u8_equals(b, b'/'); 189 | let is_pad = u8_equals(b, b'='); 190 | let is_space = u8_in_range(b, b'\t', b'\r') | u8_equals(b, b' '); 191 | 192 | let is_invalid = !(is_lower | is_upper | is_digit | is_plus | is_slash | is_pad | is_space); 193 | 194 | Self( 195 | (is_upper & b.wrapping_sub(b'A')) 196 | | (is_lower & (b.wrapping_sub(b'a').wrapping_add(26))) 197 | | (is_digit & (b.wrapping_sub(b'0').wrapping_add(52))) 198 | | (is_plus & 62) 199 | | (is_slash & 63) 200 | | (is_space & Self::WHITESPACE.0) 201 | | (is_pad & Self::PAD.0) 202 | | (is_invalid & Self::INVALID.0), 203 | ) 204 | } 205 | 206 | const fn decode_public(a: u8) -> Self { 207 | const TABLE: [CodePoint; 256] = [ 208 | // 0x00..0x0f 209 | CodePoint::INVALID, 210 | CodePoint::INVALID, 211 | CodePoint::INVALID, 212 | CodePoint::INVALID, 213 | CodePoint::INVALID, 214 | CodePoint::INVALID, 215 | CodePoint::INVALID, 216 | CodePoint::INVALID, 217 | CodePoint::INVALID, 218 | CodePoint::WHITESPACE, 219 | CodePoint::WHITESPACE, 220 | CodePoint::WHITESPACE, 221 | CodePoint::WHITESPACE, 222 | CodePoint::WHITESPACE, 223 | CodePoint::INVALID, 224 | CodePoint::INVALID, 225 | // 0x10..0x1f 226 | CodePoint::INVALID, 227 | CodePoint::INVALID, 228 | CodePoint::INVALID, 229 | CodePoint::INVALID, 230 | CodePoint::INVALID, 231 | CodePoint::INVALID, 232 | CodePoint::INVALID, 233 | CodePoint::INVALID, 234 | CodePoint::INVALID, 235 | CodePoint::INVALID, 236 | CodePoint::INVALID, 237 | CodePoint::INVALID, 238 | CodePoint::INVALID, 239 | CodePoint::INVALID, 240 | CodePoint::INVALID, 241 | CodePoint::INVALID, 242 | // 0x20..0x2f 243 | CodePoint::WHITESPACE, 244 | CodePoint::INVALID, 245 | CodePoint::INVALID, 246 | CodePoint::INVALID, 247 | CodePoint::INVALID, 248 | CodePoint::INVALID, 249 | CodePoint::INVALID, 250 | CodePoint::INVALID, 251 | CodePoint::INVALID, 252 | CodePoint::INVALID, 253 | CodePoint::INVALID, 254 | CodePoint(62), 255 | CodePoint::INVALID, 256 | CodePoint::INVALID, 257 | CodePoint::INVALID, 258 | CodePoint(63), 259 | // 0x30..0x3f 260 | CodePoint(52), 261 | CodePoint(53), 262 | CodePoint(54), 263 | CodePoint(55), 264 | CodePoint(56), 265 | CodePoint(57), 266 | CodePoint(58), 267 | CodePoint(59), 268 | CodePoint(60), 269 | CodePoint(61), 270 | CodePoint::INVALID, 271 | CodePoint::INVALID, 272 | CodePoint::INVALID, 273 | CodePoint::PAD, 274 | CodePoint::INVALID, 275 | CodePoint::INVALID, 276 | // 0x40..0x4f 277 | CodePoint::INVALID, 278 | CodePoint(0), 279 | CodePoint(1), 280 | CodePoint(2), 281 | CodePoint(3), 282 | CodePoint(4), 283 | CodePoint(5), 284 | CodePoint(6), 285 | CodePoint(7), 286 | CodePoint(8), 287 | CodePoint(9), 288 | CodePoint(10), 289 | CodePoint(11), 290 | CodePoint(12), 291 | CodePoint(13), 292 | CodePoint(14), 293 | // 0x50..0x5f 294 | CodePoint(15), 295 | CodePoint(16), 296 | CodePoint(17), 297 | CodePoint(18), 298 | CodePoint(19), 299 | CodePoint(20), 300 | CodePoint(21), 301 | CodePoint(22), 302 | CodePoint(23), 303 | CodePoint(24), 304 | CodePoint(25), 305 | CodePoint::INVALID, 306 | CodePoint::INVALID, 307 | CodePoint::INVALID, 308 | CodePoint::INVALID, 309 | CodePoint::INVALID, 310 | // 0x60..0x6f 311 | CodePoint::INVALID, 312 | CodePoint(26), 313 | CodePoint(27), 314 | CodePoint(28), 315 | CodePoint(29), 316 | CodePoint(30), 317 | CodePoint(31), 318 | CodePoint(32), 319 | CodePoint(33), 320 | CodePoint(34), 321 | CodePoint(35), 322 | CodePoint(36), 323 | CodePoint(37), 324 | CodePoint(38), 325 | CodePoint(39), 326 | CodePoint(40), 327 | // 0x70..0x7f 328 | CodePoint(41), 329 | CodePoint(42), 330 | CodePoint(43), 331 | CodePoint(44), 332 | CodePoint(45), 333 | CodePoint(46), 334 | CodePoint(47), 335 | CodePoint(48), 336 | CodePoint(49), 337 | CodePoint(50), 338 | CodePoint(51), 339 | CodePoint::INVALID, 340 | CodePoint::INVALID, 341 | CodePoint::INVALID, 342 | CodePoint::INVALID, 343 | CodePoint::INVALID, 344 | // 0x80..0x8f 345 | CodePoint::INVALID, 346 | CodePoint::INVALID, 347 | CodePoint::INVALID, 348 | CodePoint::INVALID, 349 | CodePoint::INVALID, 350 | CodePoint::INVALID, 351 | CodePoint::INVALID, 352 | CodePoint::INVALID, 353 | CodePoint::INVALID, 354 | CodePoint::INVALID, 355 | CodePoint::INVALID, 356 | CodePoint::INVALID, 357 | CodePoint::INVALID, 358 | CodePoint::INVALID, 359 | CodePoint::INVALID, 360 | CodePoint::INVALID, 361 | // 0x90..0x9f 362 | CodePoint::INVALID, 363 | CodePoint::INVALID, 364 | CodePoint::INVALID, 365 | CodePoint::INVALID, 366 | CodePoint::INVALID, 367 | CodePoint::INVALID, 368 | CodePoint::INVALID, 369 | CodePoint::INVALID, 370 | CodePoint::INVALID, 371 | CodePoint::INVALID, 372 | CodePoint::INVALID, 373 | CodePoint::INVALID, 374 | CodePoint::INVALID, 375 | CodePoint::INVALID, 376 | CodePoint::INVALID, 377 | CodePoint::INVALID, 378 | // 0xa0..0xaf 379 | CodePoint::INVALID, 380 | CodePoint::INVALID, 381 | CodePoint::INVALID, 382 | CodePoint::INVALID, 383 | CodePoint::INVALID, 384 | CodePoint::INVALID, 385 | CodePoint::INVALID, 386 | CodePoint::INVALID, 387 | CodePoint::INVALID, 388 | CodePoint::INVALID, 389 | CodePoint::INVALID, 390 | CodePoint::INVALID, 391 | CodePoint::INVALID, 392 | CodePoint::INVALID, 393 | CodePoint::INVALID, 394 | CodePoint::INVALID, 395 | // 0xb0..0xbf 396 | CodePoint::INVALID, 397 | CodePoint::INVALID, 398 | CodePoint::INVALID, 399 | CodePoint::INVALID, 400 | CodePoint::INVALID, 401 | CodePoint::INVALID, 402 | CodePoint::INVALID, 403 | CodePoint::INVALID, 404 | CodePoint::INVALID, 405 | CodePoint::INVALID, 406 | CodePoint::INVALID, 407 | CodePoint::INVALID, 408 | CodePoint::INVALID, 409 | CodePoint::INVALID, 410 | CodePoint::INVALID, 411 | CodePoint::INVALID, 412 | // 0xc0..0xcf 413 | CodePoint::INVALID, 414 | CodePoint::INVALID, 415 | CodePoint::INVALID, 416 | CodePoint::INVALID, 417 | CodePoint::INVALID, 418 | CodePoint::INVALID, 419 | CodePoint::INVALID, 420 | CodePoint::INVALID, 421 | CodePoint::INVALID, 422 | CodePoint::INVALID, 423 | CodePoint::INVALID, 424 | CodePoint::INVALID, 425 | CodePoint::INVALID, 426 | CodePoint::INVALID, 427 | CodePoint::INVALID, 428 | CodePoint::INVALID, 429 | // 0xd0..0xdf 430 | CodePoint::INVALID, 431 | CodePoint::INVALID, 432 | CodePoint::INVALID, 433 | CodePoint::INVALID, 434 | CodePoint::INVALID, 435 | CodePoint::INVALID, 436 | CodePoint::INVALID, 437 | CodePoint::INVALID, 438 | CodePoint::INVALID, 439 | CodePoint::INVALID, 440 | CodePoint::INVALID, 441 | CodePoint::INVALID, 442 | CodePoint::INVALID, 443 | CodePoint::INVALID, 444 | CodePoint::INVALID, 445 | CodePoint::INVALID, 446 | // 0xe0..0xef 447 | CodePoint::INVALID, 448 | CodePoint::INVALID, 449 | CodePoint::INVALID, 450 | CodePoint::INVALID, 451 | CodePoint::INVALID, 452 | CodePoint::INVALID, 453 | CodePoint::INVALID, 454 | CodePoint::INVALID, 455 | CodePoint::INVALID, 456 | CodePoint::INVALID, 457 | CodePoint::INVALID, 458 | CodePoint::INVALID, 459 | CodePoint::INVALID, 460 | CodePoint::INVALID, 461 | CodePoint::INVALID, 462 | CodePoint::INVALID, 463 | // 0xf0..0xff 464 | CodePoint::INVALID, 465 | CodePoint::INVALID, 466 | CodePoint::INVALID, 467 | CodePoint::INVALID, 468 | CodePoint::INVALID, 469 | CodePoint::INVALID, 470 | CodePoint::INVALID, 471 | CodePoint::INVALID, 472 | CodePoint::INVALID, 473 | CodePoint::INVALID, 474 | CodePoint::INVALID, 475 | CodePoint::INVALID, 476 | CodePoint::INVALID, 477 | CodePoint::INVALID, 478 | CodePoint::INVALID, 479 | CodePoint::INVALID, 480 | ]; 481 | 482 | TABLE[a as usize] 483 | } 484 | } 485 | 486 | /// Returns 0xff if `a` in `lo..=hi`. 487 | /// 488 | /// lo..=hi must not be 0..=255. Callers in this file have constant 489 | /// `lo` and `hi`, and this function is private to this file. 490 | fn u8_in_range(a: u8, lo: u8, hi: u8) -> u8 { 491 | debug_assert!(lo <= hi); 492 | debug_assert!(hi - lo != 255); 493 | let a = a.wrapping_sub(lo); 494 | u8_less_than(a, (hi - lo).wrapping_add(1)) 495 | } 496 | 497 | /// Returns 0xff if a < b, 0 otherwise. 498 | fn u8_less_than(a: u8, b: u8) -> u8 { 499 | let a = u16::from(a); 500 | let b = u16::from(b); 501 | u8_broadcast16(a.wrapping_sub(b)) 502 | } 503 | 504 | /// Returns 0xff if a == b, 0 otherwise. 505 | const fn u8_equals(a: u8, b: u8) -> u8 { 506 | let diff = a ^ b; 507 | u8_nonzero(diff) 508 | } 509 | 510 | /// Returns 0xff if a != 0, 0 otherwise. 511 | const fn u8_nonzero(x: u8) -> u8 { 512 | u8_broadcast8(!x & x.wrapping_sub(1)) 513 | } 514 | 515 | /// Broadcasts the top bit of `x` 516 | /// 517 | /// In other words, if the top bit of `x` is set, 518 | /// returns 0xff else 0x00. 519 | const fn u8_broadcast8(x: u8) -> u8 { 520 | let msb = x >> 7; 521 | 0u8.wrapping_sub(msb) 522 | } 523 | 524 | /// Broadcasts the top bit of `x` 525 | /// 526 | /// In other words, if the top bit of `x` is set, 527 | /// returns 0xff else 0x00. 528 | const fn u8_broadcast16(x: u16) -> u8 { 529 | let msb = x >> 15; 530 | 0u8.wrapping_sub(msb as u8) 531 | } 532 | 533 | #[cfg(all(test, feature = "alloc"))] 534 | mod tests { 535 | use super::*; 536 | 537 | #[test] 538 | fn decode_test() { 539 | assert_eq!( 540 | decode(b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), 541 | b"\x00\x10\x83\x10\x51\x87\x20\x92\x8b\x30\xd3\x8f\x41\x14\x93\x51\x55\x97\ 542 | \x61\x96\x9b\x71\xd7\x9f\x82\x18\xa3\x92\x59\xa7\xa2\x9a\xab\xb2\xdb\xaf\ 543 | \xc3\x1c\xb3\xd3\x5d\xb7\xe3\x9e\xbb\xf3\xdf\xbf" 544 | ); 545 | assert_eq!(decode(b"aGVsbG8="), b"hello"); 546 | assert_eq!(decode(b"aGVsbG8gd29ybGQ="), b"hello world"); 547 | assert_eq!(decode(b"aGVsbG8gd29ybGQh"), b"hello world!"); 548 | assert_eq!(decode(b"////"), b"\xff\xff\xff"); 549 | assert_eq!(decode(b"++++"), b"\xfb\xef\xbe"); 550 | assert_eq!(decode(b"AAAA"), b"\x00\x00\x00"); 551 | assert_eq!(decode(b"AAA="), b"\x00\x00"); 552 | assert_eq!(decode(b"AA=="), b"\x00"); 553 | 554 | // like our previous use of rust-base64, we don't require padding 555 | // if the encoding is otherwise valid given the length 556 | assert_eq!(decode(b"AAA"), b"\x00\x00"); 557 | assert_eq!(decode(b"AA"), b"\x00"); 558 | 559 | assert_eq!(decode(b""), b""); 560 | } 561 | 562 | #[test] 563 | fn decode_errors() { 564 | let mut buf = [0u8; 6]; 565 | 566 | // illegal trailing padding 567 | assert_eq!( 568 | decode_both(b"A===", &mut buf), 569 | Err(Error::InvalidTrailingPadding) 570 | ); 571 | assert_eq!( 572 | decode_both(b"====", &mut buf), 573 | Err(Error::InvalidTrailingPadding) 574 | ); 575 | assert_eq!( 576 | decode_both(b"A==", &mut buf), 577 | Err(Error::InvalidTrailingPadding) 578 | ); 579 | assert_eq!( 580 | decode_both(b"AA=", &mut buf), 581 | Err(Error::InvalidTrailingPadding) 582 | ); 583 | assert_eq!( 584 | decode_both(b"A", &mut buf), 585 | Err(Error::InvalidTrailingPadding) 586 | ); 587 | 588 | // padding before final block 589 | assert_eq!( 590 | decode_both(b"=AAAAA==", &mut buf), 591 | Err(Error::PrematurePadding) 592 | ); 593 | assert_eq!( 594 | decode_both(b"A=AAAA==", &mut buf), 595 | Err(Error::PrematurePadding) 596 | ); 597 | assert_eq!( 598 | decode_both(b"AA=AAA==", &mut buf), 599 | Err(Error::PrematurePadding) 600 | ); 601 | assert_eq!( 602 | decode_both(b"AAA=AA==", &mut buf), 603 | Err(Error::PrematurePadding) 604 | ); 605 | 606 | // illegal inputs 607 | assert_eq!( 608 | decode_both(b"%AAA", &mut buf), 609 | Err(Error::InvalidCharacter(b'%')) 610 | ); 611 | assert_eq!( 612 | decode_both(b"A%AA", &mut buf), 613 | Err(Error::InvalidCharacter(b'%')) 614 | ); 615 | assert_eq!( 616 | decode_both(b"AA%A", &mut buf), 617 | Err(Error::InvalidCharacter(b'%')) 618 | ); 619 | assert_eq!( 620 | decode_both(b"AAA%", &mut buf), 621 | Err(Error::InvalidCharacter(b'%')) 622 | ); 623 | 624 | // output sizing 625 | assert_eq!(decode_both(b"am9lIGJw", &mut [0u8; 7]), Ok(&b"joe bp"[..])); 626 | assert_eq!(decode_both(b"am9lIGJw", &mut [0u8; 6]), Ok(&b"joe bp"[..])); 627 | assert_eq!( 628 | decode_both(b"am9lIGJw", &mut [0u8; 5]), 629 | Err(Error::InsufficientOutputSpace) 630 | ); 631 | assert_eq!( 632 | decode_both(b"am9lIGJw", &mut [0u8; 4]), 633 | Err(Error::InsufficientOutputSpace) 634 | ); 635 | assert_eq!( 636 | decode_both(b"am9lIGJw", &mut [0u8; 3]), 637 | Err(Error::InsufficientOutputSpace) 638 | ); 639 | 640 | // output sizing is not pessimistic when padding is valid 641 | assert_eq!(decode_both(b"am9=", &mut [0u8; 2]), Ok(&b"jo"[..])); 642 | assert_eq!(decode_both(b"am==", &mut [0u8; 1]), Ok(&b"j"[..])); 643 | assert_eq!(decode_both(b"am9", &mut [0u8; 2]), Ok(&b"jo"[..])); 644 | assert_eq!(decode_both(b"am", &mut [0u8; 1]), Ok(&b"j"[..])); 645 | } 646 | 647 | #[test] 648 | fn check_models() { 649 | fn u8_broadcast8_model(x: u8) -> u8 { 650 | match x & 0x80 { 651 | 0x80 => 0xff, 652 | _ => 0x00, 653 | } 654 | } 655 | 656 | fn u8_broadcast16_model(x: u16) -> u8 { 657 | match x & 0x8000 { 658 | 0x8000 => 0xff, 659 | _ => 0x00, 660 | } 661 | } 662 | 663 | fn u8_nonzero_model(x: u8) -> u8 { 664 | match x { 665 | 0 => 0xff, 666 | _ => 0x00, 667 | } 668 | } 669 | 670 | fn u8_equals_model(x: u8, y: u8) -> u8 { 671 | match x == y { 672 | true => 0xff, 673 | false => 0x00, 674 | } 675 | } 676 | 677 | fn u8_in_range_model(x: u8, y: u8, z: u8) -> u8 { 678 | match (y..=z).contains(&x) { 679 | true => 0xff, 680 | false => 0x00, 681 | } 682 | } 683 | 684 | for x in u8::MIN..=u8::MAX { 685 | assert_eq!(u8_broadcast8(x), u8_broadcast8_model(x)); 686 | assert_eq!(u8_nonzero(x), u8_nonzero_model(x)); 687 | assert_eq!(CodePoint::decode_secret(x), CodePoint::decode_public(x)); 688 | 689 | for y in u8::MIN..=u8::MAX { 690 | assert_eq!(u8_equals(x, y), u8_equals_model(x, y)); 691 | 692 | let v = (x as u16) | ((y as u16) << 8); 693 | assert_eq!(u8_broadcast16(v), u8_broadcast16_model(v)); 694 | 695 | for z in y..=u8::MAX { 696 | if z - y == 255 { 697 | continue; 698 | } 699 | assert_eq!(u8_in_range(x, y, z), u8_in_range_model(x, y, z)); 700 | } 701 | } 702 | } 703 | } 704 | 705 | #[cfg(all(feature = "std", target_os = "linux", target_arch = "x86_64"))] 706 | #[test] 707 | fn codepoint_decode_secret_does_not_branch_or_index_on_secret_input() { 708 | // this is using the same theory as 709 | use crabgrind as cg; 710 | 711 | if matches!(cg::run_mode(), cg::RunMode::Native) { 712 | std::println!("SKIPPED: must be run under valgrind"); 713 | return; 714 | } 715 | 716 | let input = [b'a']; 717 | cg::monitor_command(format!( 718 | "make_memory undefined {:p} {}", 719 | input.as_ptr(), 720 | input.len() 721 | )) 722 | .unwrap(); 723 | 724 | core::hint::black_box(CodePoint::decode_secret(input[0])); 725 | } 726 | 727 | #[track_caller] 728 | fn decode(input: &[u8]) -> alloc::vec::Vec { 729 | let length = decoded_length(input.len()); 730 | 731 | let mut v = alloc::vec![0u8; length]; 732 | let used = decode_both(input, &mut v).unwrap().len(); 733 | v.truncate(used); 734 | 735 | v 736 | } 737 | 738 | fn decode_both<'a>(input: &'_ [u8], output: &'a mut [u8]) -> Result<&'a [u8], Error> { 739 | let mut output_copy = output.to_vec(); 740 | let r_pub = decode_public(input, &mut output_copy); 741 | 742 | let r_sec = decode_secret(input, output); 743 | 744 | assert_eq!(r_pub, r_sec); 745 | 746 | r_sec 747 | } 748 | } 749 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides types for representing X.509 certificates, keys and other types as 2 | //! commonly used in the rustls ecosystem. It is intended to be used by crates that need to work 3 | //! with such X.509 types, such as [rustls](https://crates.io/crates/rustls), 4 | //! [rustls-webpki](https://crates.io/crates/rustls-webpki), 5 | //! [rustls-pemfile](https://crates.io/crates/rustls-pemfile), and others. 6 | //! 7 | //! Some of these crates used to define their own trivial wrappers around DER-encoded bytes. 8 | //! However, in order to avoid inconvenient dependency edges, these were all disconnected. By 9 | //! using a common low-level crate of types with long-term stable API, we hope to avoid the 10 | //! downsides of unnecessary dependency edges while providing good interoperability between crates. 11 | //! 12 | //! ## DER and PEM 13 | //! 14 | //! Many of the types defined in this crate represent DER-encoded data. DER is a binary encoding of 15 | //! the ASN.1 format commonly used in web PKI specifications. It is a binary encoding, so it is 16 | //! relatively compact when stored in memory. However, as a binary format, it is not very easy to 17 | //! work with for humans and in contexts where binary data is inconvenient. For this reason, 18 | //! many tools and protocols use a ASCII-based encoding of DER, called PEM. In addition to the 19 | //! base64-encoded DER, PEM objects are delimited by header and footer lines which indicate the type 20 | //! of object contained in the PEM blob. 21 | //! 22 | //! Types here can be created from: 23 | //! 24 | //! - DER using (for example) [`PrivatePkcs8KeyDer::from()`]. 25 | //! - PEM using (for example) [`pem::PemObject::from_pem_slice()`]. 26 | //! 27 | //! The [`pem::PemObject`] trait contains the full selection of ways to construct 28 | //! these types from PEM encodings. That includes ways to open and read from a file, 29 | //! from a slice, or from an `std::io` stream. 30 | //! 31 | //! There is also a lower-level API that allows a given PEM file to be fully consumed 32 | //! in one pass, even if it contains different data types: see the implementation of 33 | //! the [`pem::PemObject`] trait on the `(pem::SectionKind, Vec)` tuple. 34 | //! 35 | //! ## Creating new certificates and keys 36 | //! 37 | //! This crate does not provide any functionality for creating new certificates or keys. However, 38 | //! the [rcgen](https://docs.rs/rcgen) crate can be used to create new certificates and keys. 39 | //! 40 | //! ## Cloning private keys 41 | //! 42 | //! This crate intentionally **does not** implement `Clone` on private key types in 43 | //! order to minimize the exposure of private key data in memory. 44 | //! 45 | //! If you want to extend the lifetime of a `PrivateKeyDer<'_>`, consider [`PrivateKeyDer::clone_key()`]. 46 | //! Alternatively since these types are immutable, consider wrapping the `PrivateKeyDer<'_>` in a [`Rc`] 47 | //! or an [`Arc`]. 48 | //! 49 | //! [`Rc`]: https://doc.rust-lang.org/std/rc/struct.Rc.html 50 | //! [`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html 51 | //! [`PrivateKeyDer::clone_key()`]: https://docs.rs/rustls-pki-types/latest/rustls_pki_types/enum.PrivateKeyDer.html#method.clone_key 52 | //! 53 | //! ## Target `wasm32-unknown-unknown` with the `web` feature 54 | //! 55 | //! [`std::time::SystemTime`](https://doc.rust-lang.org/std/time/struct.SystemTime.html) 56 | //! is unavailable in `wasm32-unknown-unknown` targets, so calls to 57 | //! [`UnixTime::now()`](https://docs.rs/rustls-pki-types/latest/rustls_pki_types/struct.UnixTime.html#method.now), 58 | //! otherwise enabled by the [`std`](https://docs.rs/crate/rustls-pki-types/latest/features#std) feature, 59 | //! require building instead with the [`web`](https://docs.rs/crate/rustls-pki-types/latest/features#web) 60 | //! feature. It gets time by calling [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) 61 | //! in the browser. 62 | 63 | #![cfg_attr(not(feature = "std"), no_std)] 64 | #![warn(unreachable_pub, clippy::use_self)] 65 | #![deny(missing_docs)] 66 | #![cfg_attr(rustls_pki_types_docsrs, feature(doc_cfg))] 67 | 68 | #[cfg(feature = "alloc")] 69 | extern crate alloc; 70 | 71 | #[cfg(feature = "alloc")] 72 | use alloc::vec::Vec; 73 | use core::fmt; 74 | use core::ops::Deref; 75 | use core::time::Duration; 76 | #[cfg(feature = "alloc")] 77 | use pem::{PemObject, PemObjectFilter, SectionKind}; 78 | #[cfg(all( 79 | feature = "std", 80 | not(all(target_family = "wasm", target_os = "unknown")) 81 | ))] 82 | use std::time::SystemTime; 83 | #[cfg(all(target_family = "wasm", target_os = "unknown", feature = "web"))] 84 | use web_time::SystemTime; 85 | 86 | pub mod alg_id; 87 | mod base64; 88 | mod server_name; 89 | 90 | /// Low-level PEM decoding APIs. 91 | /// 92 | /// These APIs allow decoding PEM format in an iterator, which means you 93 | /// can load multiple different types of PEM section from a file in a single 94 | /// pass. 95 | #[cfg(feature = "alloc")] 96 | pub mod pem; 97 | 98 | pub use alg_id::AlgorithmIdentifier; 99 | pub use server_name::{ 100 | AddrParseError, DnsName, InvalidDnsNameError, IpAddr, Ipv4Addr, Ipv6Addr, ServerName, 101 | }; 102 | 103 | /// A DER-encoded X.509 private key, in one of several formats 104 | /// 105 | /// See variant inner types for more detailed information. 106 | /// 107 | /// This can load several types of PEM-encoded private key, and then reveal 108 | /// which types were found: 109 | /// 110 | /// ```rust 111 | /// # #[cfg(all(feature = "alloc", feature = "std"))] { 112 | /// use rustls_pki_types::{PrivateKeyDer, pem::PemObject}; 113 | /// 114 | /// // load from a PEM file 115 | /// let pkcs8 = PrivateKeyDer::from_pem_file("tests/data/nistp256key.pkcs8.pem").unwrap(); 116 | /// let pkcs1 = PrivateKeyDer::from_pem_file("tests/data/rsa1024.pkcs1.pem").unwrap(); 117 | /// let sec1 = PrivateKeyDer::from_pem_file("tests/data/nistp256key.pem").unwrap(); 118 | /// assert!(matches!(pkcs8, PrivateKeyDer::Pkcs8(_))); 119 | /// assert!(matches!(pkcs1, PrivateKeyDer::Pkcs1(_))); 120 | /// assert!(matches!(sec1, PrivateKeyDer::Sec1(_))); 121 | /// # } 122 | /// ``` 123 | #[non_exhaustive] 124 | #[derive(Debug, PartialEq, Eq)] 125 | pub enum PrivateKeyDer<'a> { 126 | /// An RSA private key 127 | Pkcs1(PrivatePkcs1KeyDer<'a>), 128 | /// A Sec1 private key 129 | Sec1(PrivateSec1KeyDer<'a>), 130 | /// A PKCS#8 private key 131 | Pkcs8(PrivatePkcs8KeyDer<'a>), 132 | } 133 | 134 | #[cfg(feature = "alloc")] 135 | impl zeroize::Zeroize for PrivateKeyDer<'static> { 136 | fn zeroize(&mut self) { 137 | match self { 138 | Self::Pkcs1(key) => key.zeroize(), 139 | Self::Sec1(key) => key.zeroize(), 140 | Self::Pkcs8(key) => key.zeroize(), 141 | } 142 | } 143 | } 144 | 145 | impl PrivateKeyDer<'_> { 146 | /// Clone the private key to a `'static` value 147 | #[cfg(feature = "alloc")] 148 | pub fn clone_key(&self) -> PrivateKeyDer<'static> { 149 | use PrivateKeyDer::*; 150 | match self { 151 | Pkcs1(key) => Pkcs1(key.clone_key()), 152 | Sec1(key) => Sec1(key.clone_key()), 153 | Pkcs8(key) => Pkcs8(key.clone_key()), 154 | } 155 | } 156 | 157 | /// Yield the DER-encoded bytes of the private key 158 | pub fn secret_der(&self) -> &[u8] { 159 | match self { 160 | PrivateKeyDer::Pkcs1(key) => key.secret_pkcs1_der(), 161 | PrivateKeyDer::Sec1(key) => key.secret_sec1_der(), 162 | PrivateKeyDer::Pkcs8(key) => key.secret_pkcs8_der(), 163 | } 164 | } 165 | } 166 | 167 | #[cfg(feature = "alloc")] 168 | impl PemObject for PrivateKeyDer<'static> { 169 | fn from_pem(kind: SectionKind, value: Vec) -> Option { 170 | match kind { 171 | SectionKind::RsaPrivateKey => Some(Self::Pkcs1(value.into())), 172 | SectionKind::EcPrivateKey => Some(Self::Sec1(value.into())), 173 | SectionKind::PrivateKey => Some(Self::Pkcs8(value.into())), 174 | _ => None, 175 | } 176 | } 177 | } 178 | 179 | impl<'a> From> for PrivateKeyDer<'a> { 180 | fn from(key: PrivatePkcs1KeyDer<'a>) -> Self { 181 | Self::Pkcs1(key) 182 | } 183 | } 184 | 185 | impl<'a> From> for PrivateKeyDer<'a> { 186 | fn from(key: PrivateSec1KeyDer<'a>) -> Self { 187 | Self::Sec1(key) 188 | } 189 | } 190 | 191 | impl<'a> From> for PrivateKeyDer<'a> { 192 | fn from(key: PrivatePkcs8KeyDer<'a>) -> Self { 193 | Self::Pkcs8(key) 194 | } 195 | } 196 | 197 | impl<'a> TryFrom<&'a [u8]> for PrivateKeyDer<'a> { 198 | type Error = &'static str; 199 | 200 | fn try_from(key: &'a [u8]) -> Result { 201 | const SHORT_FORM_LEN_MAX: u8 = 128; 202 | const TAG_SEQUENCE: u8 = 0x30; 203 | const TAG_INTEGER: u8 = 0x02; 204 | 205 | // We expect all key formats to begin with a SEQUENCE, which requires at least 2 bytes 206 | // in the short length encoding. 207 | if key.first() != Some(&TAG_SEQUENCE) || key.len() < 2 { 208 | return Err(INVALID_KEY_DER_ERR); 209 | } 210 | 211 | // The length of the SEQUENCE is encoded in the second byte. We must skip this many bytes. 212 | let skip_len = match key[1] >= SHORT_FORM_LEN_MAX { 213 | // 1 byte for SEQUENCE tag, 1 byte for short-form len 214 | false => 2, 215 | // 1 byte for SEQUENCE tag, 1 byte for start of len, remaining bytes encoded 216 | // in key[1]. 217 | true => 2 + (key[1] - SHORT_FORM_LEN_MAX) as usize, 218 | }; 219 | let key_bytes = key.get(skip_len..).ok_or(INVALID_KEY_DER_ERR)?; 220 | 221 | // PKCS#8 (https://www.rfc-editor.org/rfc/rfc5208) describes the PrivateKeyInfo 222 | // structure as: 223 | // PrivateKeyInfo ::= SEQUENCE { 224 | // version Version, 225 | // privateKeyAlgorithm AlgorithmIdentifier {{PrivateKeyAlgorithms}}, 226 | // privateKey PrivateKey, 227 | // attributes [0] Attributes OPTIONAL 228 | // } 229 | // PKCS#5 (https://www.rfc-editor.org/rfc/rfc8018) describes the AlgorithmIdentifier 230 | // as a SEQUENCE. 231 | // 232 | // Therefore, we consider the outer SEQUENCE, a version number, and the start of 233 | // an AlgorithmIdentifier to be enough to identify a PKCS#8 key. If it were PKCS#1 or SEC1 234 | // the version would not be followed by a SEQUENCE. 235 | if matches!(key_bytes, [TAG_INTEGER, 0x01, _, TAG_SEQUENCE, ..]) { 236 | return Ok(Self::Pkcs8(key.into())); 237 | } 238 | 239 | // PKCS#1 (https://www.rfc-editor.org/rfc/rfc8017) describes the RSAPrivateKey structure 240 | // as: 241 | // RSAPrivateKey ::= SEQUENCE { 242 | // version Version, 243 | // modulus INTEGER, -- n 244 | // publicExponent INTEGER, -- e 245 | // privateExponent INTEGER, -- d 246 | // prime1 INTEGER, -- p 247 | // prime2 INTEGER, -- q 248 | // exponent1 INTEGER, -- d mod (p-1) 249 | // exponent2 INTEGER, -- d mod (q-1) 250 | // coefficient INTEGER, -- (inverse of q) mod p 251 | // otherPrimeInfos OtherPrimeInfos OPTIONAL 252 | // } 253 | // 254 | // Therefore, we consider the outer SEQUENCE and a Version of 0 to be enough to identify 255 | // a PKCS#1 key. If it were PKCS#8, the version would be followed by a SEQUENCE. If it 256 | // were SEC1, the VERSION would have been 1. 257 | if key_bytes.starts_with(&[TAG_INTEGER, 0x01, 0x00]) { 258 | return Ok(Self::Pkcs1(key.into())); 259 | } 260 | 261 | // SEC1 (https://www.rfc-editor.org/rfc/rfc5915) describes the ECPrivateKey structure as: 262 | // ECPrivateKey ::= SEQUENCE { 263 | // version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), 264 | // privateKey OCTET STRING, 265 | // parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, 266 | // publicKey [1] BIT STRING OPTIONAL 267 | // } 268 | // 269 | // Therefore, we consider the outer SEQUENCE and an INTEGER of 1 to be enough to 270 | // identify a SEC1 key. If it were PKCS#8 or PKCS#1, the version would have been 0. 271 | if key_bytes.starts_with(&[TAG_INTEGER, 0x01, 0x01]) { 272 | return Ok(Self::Sec1(key.into())); 273 | } 274 | 275 | Err(INVALID_KEY_DER_ERR) 276 | } 277 | } 278 | 279 | static INVALID_KEY_DER_ERR: &str = "unknown or invalid key format"; 280 | 281 | #[cfg(feature = "alloc")] 282 | impl TryFrom> for PrivateKeyDer<'_> { 283 | type Error = &'static str; 284 | 285 | fn try_from(key: Vec) -> Result { 286 | Ok(match PrivateKeyDer::try_from(&key[..])? { 287 | PrivateKeyDer::Pkcs1(_) => Self::Pkcs1(key.into()), 288 | PrivateKeyDer::Sec1(_) => Self::Sec1(key.into()), 289 | PrivateKeyDer::Pkcs8(_) => Self::Pkcs8(key.into()), 290 | }) 291 | } 292 | } 293 | 294 | /// A DER-encoded plaintext RSA private key; as specified in PKCS#1/RFC 3447 295 | /// 296 | /// RSA private keys are identified in PEM context as `RSA PRIVATE KEY` and when stored in a 297 | /// file usually use a `.pem` or `.key` extension. 298 | /// 299 | /// ```rust 300 | /// # #[cfg(all(feature = "alloc", feature = "std"))] { 301 | /// use rustls_pki_types::{PrivatePkcs1KeyDer, pem::PemObject}; 302 | /// 303 | /// // load from a PEM file 304 | /// PrivatePkcs1KeyDer::from_pem_file("tests/data/rsa1024.pkcs1.pem").unwrap(); 305 | /// 306 | /// // or from a PEM byte slice... 307 | /// # let byte_slice = include_bytes!("../tests/data/rsa1024.pkcs1.pem"); 308 | /// PrivatePkcs1KeyDer::from_pem_slice(byte_slice).unwrap(); 309 | /// # } 310 | /// ``` 311 | #[derive(PartialEq, Eq)] 312 | pub struct PrivatePkcs1KeyDer<'a>(Der<'a>); 313 | 314 | impl PrivatePkcs1KeyDer<'_> { 315 | /// Clone the private key to a `'static` value 316 | #[cfg(feature = "alloc")] 317 | pub fn clone_key(&self) -> PrivatePkcs1KeyDer<'static> { 318 | PrivatePkcs1KeyDer::from(self.0.as_ref().to_vec()) 319 | } 320 | 321 | /// Yield the DER-encoded bytes of the private key 322 | pub fn secret_pkcs1_der(&self) -> &[u8] { 323 | self.0.as_ref() 324 | } 325 | } 326 | 327 | #[cfg(feature = "alloc")] 328 | impl zeroize::Zeroize for PrivatePkcs1KeyDer<'static> { 329 | fn zeroize(&mut self) { 330 | self.0.0.zeroize() 331 | } 332 | } 333 | 334 | #[cfg(feature = "alloc")] 335 | impl PemObjectFilter for PrivatePkcs1KeyDer<'static> { 336 | const KIND: SectionKind = SectionKind::RsaPrivateKey; 337 | } 338 | 339 | impl<'a> From<&'a [u8]> for PrivatePkcs1KeyDer<'a> { 340 | fn from(slice: &'a [u8]) -> Self { 341 | Self(Der(BytesInner::Borrowed(slice))) 342 | } 343 | } 344 | 345 | #[cfg(feature = "alloc")] 346 | impl From> for PrivatePkcs1KeyDer<'_> { 347 | fn from(vec: Vec) -> Self { 348 | Self(Der(BytesInner::Owned(vec))) 349 | } 350 | } 351 | 352 | impl fmt::Debug for PrivatePkcs1KeyDer<'_> { 353 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 354 | f.debug_tuple("PrivatePkcs1KeyDer") 355 | .field(&"[secret key elided]") 356 | .finish() 357 | } 358 | } 359 | 360 | /// A Sec1-encoded plaintext private key; as specified in RFC 5915 361 | /// 362 | /// Sec1 private keys are identified in PEM context as `EC PRIVATE KEY` and when stored in a 363 | /// file usually use a `.pem` or `.key` extension. For more on PEM files, refer to the crate 364 | /// documentation. 365 | /// 366 | /// ```rust 367 | /// # #[cfg(all(feature = "alloc", feature = "std"))] { 368 | /// use rustls_pki_types::{PrivateSec1KeyDer, pem::PemObject}; 369 | /// 370 | /// // load from a PEM file 371 | /// PrivateSec1KeyDer::from_pem_file("tests/data/nistp256key.pem").unwrap(); 372 | /// 373 | /// // or from a PEM byte slice... 374 | /// # let byte_slice = include_bytes!("../tests/data/nistp256key.pem"); 375 | /// PrivateSec1KeyDer::from_pem_slice(byte_slice).unwrap(); 376 | /// # } 377 | /// ``` 378 | #[derive(PartialEq, Eq)] 379 | pub struct PrivateSec1KeyDer<'a>(Der<'a>); 380 | 381 | impl PrivateSec1KeyDer<'_> { 382 | /// Clone the private key to a `'static` value 383 | #[cfg(feature = "alloc")] 384 | pub fn clone_key(&self) -> PrivateSec1KeyDer<'static> { 385 | PrivateSec1KeyDer::from(self.0.as_ref().to_vec()) 386 | } 387 | 388 | /// Yield the DER-encoded bytes of the private key 389 | pub fn secret_sec1_der(&self) -> &[u8] { 390 | self.0.as_ref() 391 | } 392 | } 393 | 394 | #[cfg(feature = "alloc")] 395 | impl zeroize::Zeroize for PrivateSec1KeyDer<'static> { 396 | fn zeroize(&mut self) { 397 | self.0.0.zeroize() 398 | } 399 | } 400 | 401 | #[cfg(feature = "alloc")] 402 | impl PemObjectFilter for PrivateSec1KeyDer<'static> { 403 | const KIND: SectionKind = SectionKind::EcPrivateKey; 404 | } 405 | 406 | impl<'a> From<&'a [u8]> for PrivateSec1KeyDer<'a> { 407 | fn from(slice: &'a [u8]) -> Self { 408 | Self(Der(BytesInner::Borrowed(slice))) 409 | } 410 | } 411 | 412 | #[cfg(feature = "alloc")] 413 | impl From> for PrivateSec1KeyDer<'_> { 414 | fn from(vec: Vec) -> Self { 415 | Self(Der(BytesInner::Owned(vec))) 416 | } 417 | } 418 | 419 | impl fmt::Debug for PrivateSec1KeyDer<'_> { 420 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 421 | f.debug_tuple("PrivateSec1KeyDer") 422 | .field(&"[secret key elided]") 423 | .finish() 424 | } 425 | } 426 | 427 | /// A DER-encoded plaintext private key; as specified in PKCS#8/RFC 5958 428 | /// 429 | /// PKCS#8 private keys are identified in PEM context as `PRIVATE KEY` and when stored in a 430 | /// file usually use a `.pem` or `.key` extension. For more on PEM files, refer to the crate 431 | /// documentation. 432 | /// 433 | /// ```rust 434 | /// # #[cfg(all(feature = "alloc", feature = "std"))] { 435 | /// use rustls_pki_types::{PrivatePkcs8KeyDer, pem::PemObject}; 436 | /// 437 | /// // load from a PEM file 438 | /// PrivatePkcs8KeyDer::from_pem_file("tests/data/nistp256key.pkcs8.pem").unwrap(); 439 | /// PrivatePkcs8KeyDer::from_pem_file("tests/data/rsa1024.pkcs8.pem").unwrap(); 440 | /// 441 | /// // or from a PEM byte slice... 442 | /// # let byte_slice = include_bytes!("../tests/data/nistp256key.pkcs8.pem"); 443 | /// PrivatePkcs8KeyDer::from_pem_slice(byte_slice).unwrap(); 444 | /// # } 445 | /// ``` 446 | #[derive(PartialEq, Eq)] 447 | pub struct PrivatePkcs8KeyDer<'a>(Der<'a>); 448 | 449 | impl PrivatePkcs8KeyDer<'_> { 450 | /// Clone the private key to a `'static` value 451 | #[cfg(feature = "alloc")] 452 | pub fn clone_key(&self) -> PrivatePkcs8KeyDer<'static> { 453 | PrivatePkcs8KeyDer::from(self.0.as_ref().to_vec()) 454 | } 455 | 456 | /// Yield the DER-encoded bytes of the private key 457 | pub fn secret_pkcs8_der(&self) -> &[u8] { 458 | self.0.as_ref() 459 | } 460 | } 461 | 462 | #[cfg(feature = "alloc")] 463 | impl zeroize::Zeroize for PrivatePkcs8KeyDer<'static> { 464 | fn zeroize(&mut self) { 465 | self.0.0.zeroize() 466 | } 467 | } 468 | 469 | #[cfg(feature = "alloc")] 470 | impl PemObjectFilter for PrivatePkcs8KeyDer<'static> { 471 | const KIND: SectionKind = SectionKind::PrivateKey; 472 | } 473 | 474 | impl<'a> From<&'a [u8]> for PrivatePkcs8KeyDer<'a> { 475 | fn from(slice: &'a [u8]) -> Self { 476 | Self(Der(BytesInner::Borrowed(slice))) 477 | } 478 | } 479 | 480 | #[cfg(feature = "alloc")] 481 | impl From> for PrivatePkcs8KeyDer<'_> { 482 | fn from(vec: Vec) -> Self { 483 | Self(Der(BytesInner::Owned(vec))) 484 | } 485 | } 486 | 487 | impl fmt::Debug for PrivatePkcs8KeyDer<'_> { 488 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 489 | f.debug_tuple("PrivatePkcs8KeyDer") 490 | .field(&"[secret key elided]") 491 | .finish() 492 | } 493 | } 494 | 495 | /// A trust anchor (a.k.a. root CA) 496 | /// 497 | /// Traditionally, certificate verification libraries have represented trust anchors as full X.509 498 | /// root certificates. However, those certificates contain a lot more data than is needed for 499 | /// verifying certificates. The [`TrustAnchor`] representation allows an application to store 500 | /// just the essential elements of trust anchors. 501 | /// 502 | /// The most common way to get one of these is to call [`rustls_webpki::anchor_from_trusted_cert()`]. 503 | /// 504 | /// [`rustls_webpki::anchor_from_trusted_cert()`]: https://docs.rs/rustls-webpki/latest/webpki/fn.anchor_from_trusted_cert.html 505 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 506 | pub struct TrustAnchor<'a> { 507 | /// Value of the `subject` field of the trust anchor 508 | pub subject: Der<'a>, 509 | /// Value of the `subjectPublicKeyInfo` field of the trust anchor 510 | pub subject_public_key_info: Der<'a>, 511 | /// Value of DER-encoded `NameConstraints`, containing name constraints to the trust anchor, if any 512 | pub name_constraints: Option>, 513 | } 514 | 515 | impl TrustAnchor<'_> { 516 | /// Yield a `'static` lifetime of the `TrustAnchor` by allocating owned `Der` variants 517 | #[cfg(feature = "alloc")] 518 | pub fn to_owned(&self) -> TrustAnchor<'static> { 519 | #[cfg(not(feature = "std"))] 520 | use alloc::borrow::ToOwned; 521 | TrustAnchor { 522 | subject: self.subject.as_ref().to_owned().into(), 523 | subject_public_key_info: self.subject_public_key_info.as_ref().to_owned().into(), 524 | name_constraints: self 525 | .name_constraints 526 | .as_ref() 527 | .map(|nc| nc.as_ref().to_owned().into()), 528 | } 529 | } 530 | } 531 | 532 | /// A Certificate Revocation List; as specified in RFC 5280 533 | /// 534 | /// Certificate revocation lists are identified in PEM context as `X509 CRL` and when stored in a 535 | /// file usually use a `.crl` extension. For more on PEM files, refer to the crate documentation. 536 | /// 537 | /// ```rust 538 | /// # #[cfg(all(feature = "alloc", feature = "std"))] { 539 | /// use rustls_pki_types::{CertificateRevocationListDer, pem::PemObject}; 540 | /// 541 | /// // load several from a PEM file 542 | /// let crls: Vec<_> = CertificateRevocationListDer::pem_file_iter("tests/data/crl.pem") 543 | /// .unwrap() 544 | /// .collect(); 545 | /// assert!(crls.len() >= 1); 546 | /// 547 | /// // or one from a PEM byte slice... 548 | /// # let byte_slice = include_bytes!("../tests/data/crl.pem"); 549 | /// CertificateRevocationListDer::from_pem_slice(byte_slice).unwrap(); 550 | /// 551 | /// // or several from a PEM byte slice 552 | /// let crls: Vec<_> = CertificateRevocationListDer::pem_slice_iter(byte_slice) 553 | /// .collect(); 554 | /// assert!(crls.len() >= 1); 555 | /// # } 556 | /// ``` 557 | 558 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 559 | pub struct CertificateRevocationListDer<'a>(Der<'a>); 560 | 561 | #[cfg(feature = "alloc")] 562 | impl PemObjectFilter for CertificateRevocationListDer<'static> { 563 | const KIND: SectionKind = SectionKind::Crl; 564 | } 565 | 566 | impl AsRef<[u8]> for CertificateRevocationListDer<'_> { 567 | fn as_ref(&self) -> &[u8] { 568 | self.0.as_ref() 569 | } 570 | } 571 | 572 | impl Deref for CertificateRevocationListDer<'_> { 573 | type Target = [u8]; 574 | 575 | fn deref(&self) -> &Self::Target { 576 | self.as_ref() 577 | } 578 | } 579 | 580 | impl<'a> From<&'a [u8]> for CertificateRevocationListDer<'a> { 581 | fn from(slice: &'a [u8]) -> Self { 582 | Self(Der::from(slice)) 583 | } 584 | } 585 | 586 | #[cfg(feature = "alloc")] 587 | impl From> for CertificateRevocationListDer<'_> { 588 | fn from(vec: Vec) -> Self { 589 | Self(Der::from(vec)) 590 | } 591 | } 592 | 593 | /// A Certificate Signing Request; as specified in RFC 2986 594 | /// 595 | /// Certificate signing requests are identified in PEM context as `CERTIFICATE REQUEST` and when stored in a 596 | /// file usually use a `.csr` extension. For more on PEM files, refer to the crate documentation. 597 | /// 598 | /// ```rust 599 | /// # #[cfg(all(feature = "alloc", feature = "std"))] { 600 | /// use rustls_pki_types::{CertificateSigningRequestDer, pem::PemObject}; 601 | /// 602 | /// // load from a PEM file 603 | /// CertificateSigningRequestDer::from_pem_file("tests/data/csr.pem").unwrap(); 604 | /// 605 | /// // or from a PEM byte slice... 606 | /// # let byte_slice = include_bytes!("../tests/data/csr.pem"); 607 | /// CertificateSigningRequestDer::from_pem_slice(byte_slice).unwrap(); 608 | /// # } 609 | /// ``` 610 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 611 | pub struct CertificateSigningRequestDer<'a>(Der<'a>); 612 | 613 | #[cfg(feature = "alloc")] 614 | impl PemObjectFilter for CertificateSigningRequestDer<'static> { 615 | const KIND: SectionKind = SectionKind::Csr; 616 | } 617 | 618 | impl AsRef<[u8]> for CertificateSigningRequestDer<'_> { 619 | fn as_ref(&self) -> &[u8] { 620 | self.0.as_ref() 621 | } 622 | } 623 | 624 | impl Deref for CertificateSigningRequestDer<'_> { 625 | type Target = [u8]; 626 | 627 | fn deref(&self) -> &Self::Target { 628 | self.as_ref() 629 | } 630 | } 631 | 632 | impl<'a> From<&'a [u8]> for CertificateSigningRequestDer<'a> { 633 | fn from(slice: &'a [u8]) -> Self { 634 | Self(Der::from(slice)) 635 | } 636 | } 637 | 638 | #[cfg(feature = "alloc")] 639 | impl From> for CertificateSigningRequestDer<'_> { 640 | fn from(vec: Vec) -> Self { 641 | Self(Der::from(vec)) 642 | } 643 | } 644 | 645 | /// A DER-encoded X.509 certificate; as specified in RFC 5280 646 | /// 647 | /// Certificates are identified in PEM context as `CERTIFICATE` and when stored in a 648 | /// file usually use a `.pem`, `.cer` or `.crt` extension. For more on PEM files, refer to the 649 | /// crate documentation. 650 | /// 651 | /// ```rust 652 | /// # #[cfg(all(feature = "alloc", feature = "std"))] { 653 | /// use rustls_pki_types::{CertificateDer, pem::PemObject}; 654 | /// 655 | /// // load several from a PEM file 656 | /// let certs: Vec<_> = CertificateDer::pem_file_iter("tests/data/certificate.chain.pem") 657 | /// .unwrap() 658 | /// .collect(); 659 | /// assert_eq!(certs.len(), 3); 660 | /// 661 | /// // or one from a PEM byte slice... 662 | /// # let byte_slice = include_bytes!("../tests/data/certificate.chain.pem"); 663 | /// CertificateDer::from_pem_slice(byte_slice).unwrap(); 664 | /// 665 | /// // or several from a PEM byte slice 666 | /// let certs: Vec<_> = CertificateDer::pem_slice_iter(byte_slice) 667 | /// .collect(); 668 | /// assert_eq!(certs.len(), 3); 669 | /// # } 670 | /// ``` 671 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 672 | pub struct CertificateDer<'a>(Der<'a>); 673 | 674 | impl<'a> CertificateDer<'a> { 675 | /// A const constructor to create a `CertificateDer` from a slice of DER. 676 | pub const fn from_slice(bytes: &'a [u8]) -> Self { 677 | Self(Der::from_slice(bytes)) 678 | } 679 | } 680 | 681 | #[cfg(feature = "alloc")] 682 | impl PemObjectFilter for CertificateDer<'static> { 683 | const KIND: SectionKind = SectionKind::Certificate; 684 | } 685 | 686 | impl AsRef<[u8]> for CertificateDer<'_> { 687 | fn as_ref(&self) -> &[u8] { 688 | self.0.as_ref() 689 | } 690 | } 691 | 692 | impl Deref for CertificateDer<'_> { 693 | type Target = [u8]; 694 | 695 | fn deref(&self) -> &Self::Target { 696 | self.as_ref() 697 | } 698 | } 699 | 700 | impl<'a> From<&'a [u8]> for CertificateDer<'a> { 701 | fn from(slice: &'a [u8]) -> Self { 702 | Self(Der::from(slice)) 703 | } 704 | } 705 | 706 | #[cfg(feature = "alloc")] 707 | impl From> for CertificateDer<'_> { 708 | fn from(vec: Vec) -> Self { 709 | Self(Der::from(vec)) 710 | } 711 | } 712 | 713 | impl CertificateDer<'_> { 714 | /// Converts this certificate into its owned variant, unfreezing borrowed content (if any) 715 | #[cfg(feature = "alloc")] 716 | pub fn into_owned(self) -> CertificateDer<'static> { 717 | CertificateDer(Der(self.0.0.into_owned())) 718 | } 719 | } 720 | 721 | /// A DER-encoded SubjectPublicKeyInfo (SPKI), as specified in RFC 5280. 722 | #[deprecated(since = "1.7.0", note = "Prefer `SubjectPublicKeyInfoDer` instead")] 723 | pub type SubjectPublicKeyInfo<'a> = SubjectPublicKeyInfoDer<'a>; 724 | 725 | /// A DER-encoded SubjectPublicKeyInfo (SPKI), as specified in RFC 5280. 726 | /// 727 | /// Public keys are identified in PEM context as a `PUBLIC KEY`. 728 | /// 729 | /// ```rust 730 | /// # #[cfg(all(feature = "alloc", feature = "std"))] { 731 | /// use rustls_pki_types::{SubjectPublicKeyInfoDer, pem::PemObject}; 732 | /// 733 | /// // load from a PEM file 734 | /// SubjectPublicKeyInfoDer::from_pem_file("tests/data/spki.pem").unwrap(); 735 | /// 736 | /// // or from a PEM byte slice... 737 | /// # let byte_slice = include_bytes!("../tests/data/spki.pem"); 738 | /// SubjectPublicKeyInfoDer::from_pem_slice(byte_slice).unwrap(); 739 | /// # } 740 | /// ``` 741 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 742 | pub struct SubjectPublicKeyInfoDer<'a>(Der<'a>); 743 | 744 | #[cfg(feature = "alloc")] 745 | impl PemObjectFilter for SubjectPublicKeyInfoDer<'static> { 746 | const KIND: SectionKind = SectionKind::PublicKey; 747 | } 748 | 749 | impl AsRef<[u8]> for SubjectPublicKeyInfoDer<'_> { 750 | fn as_ref(&self) -> &[u8] { 751 | self.0.as_ref() 752 | } 753 | } 754 | 755 | impl Deref for SubjectPublicKeyInfoDer<'_> { 756 | type Target = [u8]; 757 | 758 | fn deref(&self) -> &Self::Target { 759 | self.as_ref() 760 | } 761 | } 762 | 763 | impl<'a> From<&'a [u8]> for SubjectPublicKeyInfoDer<'a> { 764 | fn from(slice: &'a [u8]) -> Self { 765 | Self(Der::from(slice)) 766 | } 767 | } 768 | 769 | #[cfg(feature = "alloc")] 770 | impl From> for SubjectPublicKeyInfoDer<'_> { 771 | fn from(vec: Vec) -> Self { 772 | Self(Der::from(vec)) 773 | } 774 | } 775 | 776 | impl SubjectPublicKeyInfoDer<'_> { 777 | /// Converts this SubjectPublicKeyInfo into its owned variant, unfreezing borrowed content (if any) 778 | #[cfg(feature = "alloc")] 779 | pub fn into_owned(self) -> SubjectPublicKeyInfoDer<'static> { 780 | SubjectPublicKeyInfoDer(Der(self.0.0.into_owned())) 781 | } 782 | } 783 | 784 | /// A TLS-encoded Encrypted Client Hello (ECH) configuration list (`ECHConfigList`); as specified in 785 | /// [draft-ietf-tls-esni-18 §4](https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-18#section-4) 786 | #[derive(Clone, Eq, Hash, PartialEq)] 787 | pub struct EchConfigListBytes<'a>(BytesInner<'a>); 788 | 789 | impl EchConfigListBytes<'_> { 790 | /// Converts this config into its owned variant, unfreezing borrowed content (if any) 791 | #[cfg(feature = "alloc")] 792 | pub fn into_owned(self) -> EchConfigListBytes<'static> { 793 | EchConfigListBytes(self.0.into_owned()) 794 | } 795 | } 796 | 797 | #[cfg(feature = "alloc")] 798 | impl EchConfigListBytes<'static> { 799 | /// Convert an iterator over PEM items into an `EchConfigListBytes` and private key. 800 | /// 801 | /// This handles the "ECHConfig file" format specified in 802 | /// 803 | /// 804 | /// Use it like: 805 | /// 806 | /// ```rust 807 | /// # #[cfg(all(feature = "alloc", feature = "std"))] { 808 | /// # use rustls_pki_types::{EchConfigListBytes, pem::PemObject}; 809 | /// let (config, key) = EchConfigListBytes::config_and_key_from_iter( 810 | /// PemObject::pem_file_iter("tests/data/ech.pem").unwrap() 811 | /// ).unwrap(); 812 | /// # } 813 | /// ``` 814 | pub fn config_and_key_from_iter( 815 | iter: impl Iterator), pem::Error>>, 816 | ) -> Result<(Self, PrivatePkcs8KeyDer<'static>), pem::Error> { 817 | let mut key = None; 818 | let mut config = None; 819 | 820 | for item in iter { 821 | let (kind, data) = item?; 822 | match kind { 823 | SectionKind::PrivateKey => { 824 | key = PrivatePkcs8KeyDer::from_pem(kind, data); 825 | } 826 | SectionKind::EchConfigList => { 827 | config = Self::from_pem(kind, data); 828 | } 829 | _ => continue, 830 | }; 831 | 832 | if let (Some(_key), Some(_config)) = (&key, &config) { 833 | return Ok((config.take().unwrap(), key.take().unwrap())); 834 | } 835 | } 836 | 837 | Err(pem::Error::NoItemsFound) 838 | } 839 | } 840 | 841 | #[cfg(feature = "alloc")] 842 | impl PemObjectFilter for EchConfigListBytes<'static> { 843 | const KIND: SectionKind = SectionKind::EchConfigList; 844 | } 845 | 846 | impl fmt::Debug for EchConfigListBytes<'_> { 847 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 848 | hex(f, self.as_ref()) 849 | } 850 | } 851 | 852 | impl AsRef<[u8]> for EchConfigListBytes<'_> { 853 | fn as_ref(&self) -> &[u8] { 854 | self.0.as_ref() 855 | } 856 | } 857 | 858 | impl Deref for EchConfigListBytes<'_> { 859 | type Target = [u8]; 860 | 861 | fn deref(&self) -> &Self::Target { 862 | self.as_ref() 863 | } 864 | } 865 | 866 | impl<'a> From<&'a [u8]> for EchConfigListBytes<'a> { 867 | fn from(slice: &'a [u8]) -> Self { 868 | Self(BytesInner::Borrowed(slice)) 869 | } 870 | } 871 | 872 | #[cfg(feature = "alloc")] 873 | impl From> for EchConfigListBytes<'_> { 874 | fn from(vec: Vec) -> Self { 875 | Self(BytesInner::Owned(vec)) 876 | } 877 | } 878 | 879 | /// An abstract signature verification algorithm. 880 | /// 881 | /// One of these is needed per supported pair of public key type (identified 882 | /// with `public_key_alg_id()`) and `signatureAlgorithm` (identified with 883 | /// `signature_alg_id()`). Note that both of these `AlgorithmIdentifier`s include 884 | /// the parameters encoding, so separate `SignatureVerificationAlgorithm`s are needed 885 | /// for each possible public key or signature parameters. 886 | /// 887 | /// Debug implementations should list the public key algorithm identifier and 888 | /// signature algorithm identifier in human friendly form (i.e. not encoded bytes), 889 | /// along with the name of the implementing library (to distinguish different 890 | /// implementations of the same algorithms). 891 | pub trait SignatureVerificationAlgorithm: Send + Sync + fmt::Debug { 892 | /// Verify a signature. 893 | /// 894 | /// `public_key` is the `subjectPublicKey` value from a `SubjectPublicKeyInfo` encoding 895 | /// and is untrusted. The key's `subjectPublicKeyInfo` matches the [`AlgorithmIdentifier`] 896 | /// returned by `public_key_alg_id()`. 897 | /// 898 | /// `message` is the data over which the signature was allegedly computed. 899 | /// It is not hashed; implementations of this trait function must do hashing 900 | /// if that is required by the algorithm they implement. 901 | /// 902 | /// `signature` is the signature allegedly over `message`. 903 | /// 904 | /// Return `Ok(())` only if `signature` is a valid signature on `message`. 905 | /// 906 | /// Return `Err(InvalidSignature)` if the signature is invalid, including if the `public_key` 907 | /// encoding is invalid. There is no need or opportunity to produce errors 908 | /// that are more specific than this. 909 | fn verify_signature( 910 | &self, 911 | public_key: &[u8], 912 | message: &[u8], 913 | signature: &[u8], 914 | ) -> Result<(), InvalidSignature>; 915 | 916 | /// Return the `AlgorithmIdentifier` that must equal a public key's 917 | /// `subjectPublicKeyInfo` value for this `SignatureVerificationAlgorithm` 918 | /// to be used for signature verification. 919 | fn public_key_alg_id(&self) -> AlgorithmIdentifier; 920 | 921 | /// Return the `AlgorithmIdentifier` that must equal the `signatureAlgorithm` value 922 | /// on the data to be verified for this `SignatureVerificationAlgorithm` to be used 923 | /// for signature verification. 924 | fn signature_alg_id(&self) -> AlgorithmIdentifier; 925 | 926 | /// Return `true` if this is backed by a FIPS-approved implementation. 927 | fn fips(&self) -> bool { 928 | false 929 | } 930 | } 931 | 932 | /// A detail-less error when a signature is not valid. 933 | #[derive(Debug, Copy, Clone)] 934 | pub struct InvalidSignature; 935 | 936 | /// A timestamp, tracking the number of non-leap seconds since the Unix epoch. 937 | /// 938 | /// The Unix epoch is defined January 1, 1970 00:00:00 UTC. 939 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] 940 | pub struct UnixTime(u64); 941 | 942 | impl UnixTime { 943 | /// The current time, as a `UnixTime` 944 | #[cfg(any( 945 | all( 946 | feature = "std", 947 | not(all(target_family = "wasm", target_os = "unknown")) 948 | ), 949 | all(target_family = "wasm", target_os = "unknown", feature = "web") 950 | ))] 951 | pub fn now() -> Self { 952 | Self::since_unix_epoch( 953 | SystemTime::now() 954 | .duration_since(SystemTime::UNIX_EPOCH) 955 | .unwrap(), // Safe: this code did not exist before 1970. 956 | ) 957 | } 958 | 959 | /// Convert a `Duration` since the start of 1970 to a `UnixTime` 960 | /// 961 | /// The `duration` must be relative to the Unix epoch. 962 | pub const fn since_unix_epoch(duration: Duration) -> Self { 963 | Self(duration.as_secs()) 964 | } 965 | 966 | /// Number of seconds since the Unix epoch 967 | pub const fn as_secs(&self) -> u64 { 968 | self.0 969 | } 970 | } 971 | 972 | /// DER-encoded data, either owned or borrowed 973 | /// 974 | /// This wrapper type is used to represent DER-encoded data in a way that is agnostic to whether 975 | /// the data is owned (by a `Vec`) or borrowed (by a `&[u8]`). Support for the owned 976 | /// variant is only available when the `alloc` feature is enabled. 977 | #[derive(Clone, Eq, Hash, PartialEq)] 978 | pub struct Der<'a>(BytesInner<'a>); 979 | 980 | impl<'a> Der<'a> { 981 | /// A const constructor to create a `Der` from a borrowed slice 982 | pub const fn from_slice(der: &'a [u8]) -> Self { 983 | Self(BytesInner::Borrowed(der)) 984 | } 985 | } 986 | 987 | impl AsRef<[u8]> for Der<'_> { 988 | fn as_ref(&self) -> &[u8] { 989 | self.0.as_ref() 990 | } 991 | } 992 | 993 | impl Deref for Der<'_> { 994 | type Target = [u8]; 995 | 996 | fn deref(&self) -> &Self::Target { 997 | self.as_ref() 998 | } 999 | } 1000 | 1001 | impl<'a> From<&'a [u8]> for Der<'a> { 1002 | fn from(slice: &'a [u8]) -> Self { 1003 | Self(BytesInner::Borrowed(slice)) 1004 | } 1005 | } 1006 | 1007 | #[cfg(feature = "alloc")] 1008 | impl From> for Der<'static> { 1009 | fn from(vec: Vec) -> Self { 1010 | Self(BytesInner::Owned(vec)) 1011 | } 1012 | } 1013 | 1014 | impl fmt::Debug for Der<'_> { 1015 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1016 | hex(f, self.as_ref()) 1017 | } 1018 | } 1019 | 1020 | #[derive(Debug, Clone)] 1021 | enum BytesInner<'a> { 1022 | #[cfg(feature = "alloc")] 1023 | Owned(Vec), 1024 | Borrowed(&'a [u8]), 1025 | } 1026 | 1027 | #[cfg(feature = "alloc")] 1028 | impl BytesInner<'_> { 1029 | fn into_owned(self) -> BytesInner<'static> { 1030 | BytesInner::Owned(match self { 1031 | Self::Owned(vec) => vec, 1032 | Self::Borrowed(slice) => slice.to_vec(), 1033 | }) 1034 | } 1035 | } 1036 | 1037 | #[cfg(feature = "alloc")] 1038 | impl zeroize::Zeroize for BytesInner<'static> { 1039 | fn zeroize(&mut self) { 1040 | match self { 1041 | BytesInner::Owned(vec) => vec.zeroize(), 1042 | BytesInner::Borrowed(_) => (), 1043 | } 1044 | } 1045 | } 1046 | 1047 | impl AsRef<[u8]> for BytesInner<'_> { 1048 | fn as_ref(&self) -> &[u8] { 1049 | match &self { 1050 | #[cfg(feature = "alloc")] 1051 | BytesInner::Owned(vec) => vec.as_ref(), 1052 | BytesInner::Borrowed(slice) => slice, 1053 | } 1054 | } 1055 | } 1056 | 1057 | impl core::hash::Hash for BytesInner<'_> { 1058 | fn hash(&self, state: &mut H) { 1059 | state.write(self.as_ref()); 1060 | } 1061 | } 1062 | 1063 | impl PartialEq for BytesInner<'_> { 1064 | fn eq(&self, other: &Self) -> bool { 1065 | self.as_ref() == other.as_ref() 1066 | } 1067 | } 1068 | 1069 | impl Eq for BytesInner<'_> {} 1070 | 1071 | // Format an iterator of u8 into a hex string 1072 | fn hex<'a>(f: &mut fmt::Formatter<'_>, payload: impl IntoIterator) -> fmt::Result { 1073 | for (i, b) in payload.into_iter().enumerate() { 1074 | if i == 0 { 1075 | write!(f, "0x")?; 1076 | } 1077 | write!(f, "{b:02x}")?; 1078 | } 1079 | Ok(()) 1080 | } 1081 | 1082 | #[cfg(all(test, feature = "std"))] 1083 | mod tests { 1084 | use super::*; 1085 | 1086 | #[test] 1087 | fn der_debug() { 1088 | let der = Der::from_slice(&[0x01, 0x02, 0x03]); 1089 | assert_eq!(format!("{der:?}"), "0x010203"); 1090 | } 1091 | 1092 | #[test] 1093 | fn alg_id_debug() { 1094 | let alg_id = AlgorithmIdentifier::from_slice(&[0x01, 0x02, 0x03]); 1095 | assert_eq!(format!("{alg_id:?}"), "0x010203"); 1096 | } 1097 | 1098 | #[test] 1099 | fn bytes_inner_equality() { 1100 | let owned_a = BytesInner::Owned(vec![1, 2, 3]); 1101 | let owned_b = BytesInner::Owned(vec![4, 5]); 1102 | let borrowed_a = BytesInner::Borrowed(&[1, 2, 3]); 1103 | let borrowed_b = BytesInner::Borrowed(&[99]); 1104 | 1105 | // Self-equality. 1106 | assert_eq!(owned_a, owned_a); 1107 | assert_eq!(owned_b, owned_b); 1108 | assert_eq!(borrowed_a, borrowed_a); 1109 | assert_eq!(borrowed_b, borrowed_b); 1110 | 1111 | // Borrowed vs Owned equality 1112 | assert_eq!(owned_a, borrowed_a); 1113 | assert_eq!(borrowed_a, owned_a); 1114 | 1115 | // Owned inequality 1116 | assert_ne!(owned_a, owned_b); 1117 | assert_ne!(owned_b, owned_a); 1118 | 1119 | // Borrowed inequality 1120 | assert_ne!(borrowed_a, borrowed_b); 1121 | assert_ne!(borrowed_b, borrowed_a); 1122 | 1123 | // Borrowed vs Owned inequality 1124 | assert_ne!(owned_a, borrowed_b); 1125 | assert_ne!(borrowed_b, owned_a); 1126 | } 1127 | } 1128 | --------------------------------------------------------------------------------