diff --git a/.github/workflows/blobby.yml b/.github/workflows/blobby.yml index 2834a42f..923d731c 100644 --- a/.github/workflows/blobby.yml +++ b/.github/workflows/blobby.yml @@ -25,10 +25,16 @@ jobs: - 1.39.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo test --release + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} diff --git a/.github/workflows/block-buffer.yml b/.github/workflows/block-buffer.yml index c3065bbc..29ed2613 100644 --- a/.github/workflows/block-buffer.yml +++ b/.github/workflows/block-buffer.yml @@ -28,7 +28,8 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -36,7 +37,12 @@ jobs: target: ${{ matrix.target }} override: true - run: cargo build --release --target ${{ matrix.target }} - - run: cargo build --features block-padding --release --target ${{ matrix.target }} + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + test: runs-on: ubuntu-latest strategy: @@ -45,12 +51,12 @@ jobs: - 1.41.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release - - run: cargo test --features block-padding --release - - run: cargo test --all-features --release + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo test --release + - run: cargo test --all-features --release diff --git a/.github/workflows/block-padding.yml b/.github/workflows/block-padding.yml index 68e43f40..36e20d18 100644 --- a/.github/workflows/block-padding.yml +++ b/.github/workflows/block-padding.yml @@ -22,32 +22,40 @@ jobs: strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable target: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} override: true - - run: cargo build --release --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + test: runs-on: ubuntu-latest strategy: matrix: rust: - - 1.41.0 # MSRV + - 1.56.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo test diff --git a/.github/workflows/cmov.yml b/.github/workflows/cmov.yml new file mode 100644 index 00000000..5a6292b2 --- /dev/null +++ b/.github/workflows/cmov.yml @@ -0,0 +1,113 @@ +name: cmov + +on: + pull_request: + paths: + - "cmov/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: cmov + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.59.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo build --release --target ${{ matrix.target }} + + test: + strategy: + matrix: + include: + # 32-bit Linux + - target: i686-unknown-linux-gnu + platform: ubuntu-latest + rust: 1.59.0 # MSRV + deps: sudo apt update && sudo apt install gcc-multilib + + # 64-bit Linux + - target: x86_64-unknown-linux-gnu + platform: ubuntu-latest + rust: 1.59.0 # MSRV + + # 64-bit Windows + - target: x86_64-pc-windows-msvc + platform: windows-latest + rust: 1.59.0 # MSRV + + # 64-bit macOS + - target: x86_64-apple-darwin + platform: macos-latest + rust: 1.59.0 # MSRV + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + profile: minimal + override: true + - run: ${{ matrix.deps }} + - run: cargo test --release + + # Cross-compiled tests + cross: + strategy: + matrix: + include: + # ARM64 + - target: aarch64-unknown-linux-gnu + rust: 1.59.0 # MSRV + - target: aarch64-unknown-linux-gnu + rust: stable + + # PPC32 + - target: powerpc-unknown-linux-gnu + rust: 1.59.0 # MSRV + - target: powerpc-unknown-linux-gnu + rust: stable + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + profile: minimal + override: true + - uses: RustCrypto/actions/cross-install@master + - run: cross test --release --target ${{ matrix.target }} diff --git a/.github/workflows/const-oid.yml b/.github/workflows/const-oid.yml deleted file mode 100644 index 1c8fe6f3..00000000 --- a/.github/workflows/const-oid.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: const-oid - -on: - pull_request: - paths: - - "const-oid/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: const-oid - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --target ${{ matrix.target }} --release - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release - - run: cargo test --release --all-features diff --git a/.github/workflows/cpufeatures.yml b/.github/workflows/cpufeatures.yml index 8d6828e8..bbf409b6 100644 --- a/.github/workflows/cpufeatures.yml +++ b/.github/workflows/cpufeatures.yml @@ -17,6 +17,11 @@ env: RUSTFLAGS: "-Dwarnings" jobs: + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + # Linux tests linux: strategy: @@ -38,6 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} @@ -57,6 +63,7 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -79,6 +86,7 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -101,6 +109,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} diff --git a/.github/workflows/crypto-bigint.yml b/.github/workflows/crypto-bigint.yml deleted file mode 100644 index 55b69f6d..00000000 --- a/.github/workflows/crypto-bigint.yml +++ /dev/null @@ -1,119 +0,0 @@ -name: crypto-bigint - -on: - pull_request: - paths: - - "crypto-bigint/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: crypto-bigint - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --target ${{ matrix.target }} --release - - run: cargo build --target ${{ matrix.target }} --release --features generic-array - - run: cargo build --target ${{ matrix.target }} --release --features zeroize - - test: - runs-on: ubuntu-latest - strategy: - matrix: - include: - # 32-bit Linux - - target: i686-unknown-linux-gnu - rust: 1.51.0 # MSRV - deps: sudo apt update && sudo apt install gcc-multilib - - target: i686-unknown-linux-gnu - rust: stable - deps: sudo apt update && sudo apt install gcc-multilib - - # 64-bit Linux - - target: x86_64-unknown-linux-gnu - rust: 1.51.0 # MSRV - - target: x86_64-unknown-linux-gnu - rust: stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - profile: minimal - override: true - - run: ${{ matrix.deps }} - - run: cargo test --target ${{ matrix.target }} --release - - run: cargo test --target ${{ matrix.target }} --release --features generic-array - - run: cargo test --target ${{ matrix.target }} --release --features zeroize - - run: cargo test --target ${{ matrix.target }} --release --all-features - - proptests: - runs-on: ubuntu-latest - defaults: - run: - working-directory: crypto-bigint/proptests - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - profile: minimal - override: true - - run: cargo test - - # Cross-compiled tests - cross: - strategy: - matrix: - include: - # ARM64 - - target: aarch64-unknown-linux-gnu - rust: 1.51.0 # MSRV - - target: aarch64-unknown-linux-gnu - rust: stable - - # PPC32 - - target: powerpc-unknown-linux-gnu - rust: 1.51.0 # MSRV - - target: powerpc-unknown-linux-gnu - rust: stable - - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - run: ${{ matrix.deps }} - - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - profile: minimal - override: true - - run: cargo install cross - - run: cross test --target ${{ matrix.target }} --release - - run: cross test --target ${{ matrix.target }} --release --features generic-array - - run: cross test --target ${{ matrix.target }} --release --features zeroize - - run: cross test --target ${{ matrix.target }} --release --all-features diff --git a/.github/workflows/dbl.yml b/.github/workflows/dbl.yml index 8d431bbb..478f7659 100644 --- a/.github/workflows/dbl.yml +++ b/.github/workflows/dbl.yml @@ -28,7 +28,8 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -36,6 +37,12 @@ jobs: target: ${{ matrix.target }} override: true - run: cargo build --release --target ${{ matrix.target }} + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + test: runs-on: ubuntu-latest strategy: @@ -44,10 +51,11 @@ jobs: - 1.41.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo test --release diff --git a/.github/workflows/der.yml b/.github/workflows/der.yml deleted file mode 100644 index 5bfe0400..00000000 --- a/.github/workflows/der.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: der - -on: - pull_request: - paths: - - "const-oid/**" - - "der/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: der - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --target ${{ matrix.target }} --release - - run: cargo build --target ${{ matrix.target }} --release --features bigint - - run: cargo build --target ${{ matrix.target }} --release --features oid - - run: cargo build --target ${{ matrix.target }} --release --features bigint,oid - - test: - runs-on: ubuntu-latest - strategy: - matrix: - include: - # 32-bit Linux - - target: i686-unknown-linux-gnu - rust: 1.51.0 # MSRV - deps: sudo apt update && sudo apt install gcc-multilib - - target: i686-unknown-linux-gnu - rust: stable - deps: sudo apt update && sudo apt install gcc-multilib - - # 64-bit Linux - - target: x86_64-unknown-linux-gnu - rust: 1.51.0 # MSRV - - target: x86_64-unknown-linux-gnu - rust: stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - profile: minimal - override: true - - run: ${{ matrix.deps }} - - run: cargo test --target ${{ matrix.target }} --release - - run: cargo test --target ${{ matrix.target }} --release --features bigint - - run: cargo test --target ${{ matrix.target }} --release --features oid - - run: cargo test --target ${{ matrix.target }} --release --all-features diff --git a/.github/workflows/hex-literal.yml b/.github/workflows/hex-literal.yml index 0eecb706..130dcd49 100644 --- a/.github/workflows/hex-literal.yml +++ b/.github/workflows/hex-literal.yml @@ -28,7 +28,8 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -36,6 +37,12 @@ jobs: target: ${{ matrix.target }} override: true - run: cargo build --release --target ${{ matrix.target }} + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + test: runs-on: ubuntu-latest strategy: @@ -44,10 +51,11 @@ jobs: - 1.45.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo test --release diff --git a/.github/workflows/base64ct.yml b/.github/workflows/inout.yml similarity index 51% rename from .github/workflows/base64ct.yml rename to .github/workflows/inout.yml index 4acfe445..886e007a 100644 --- a/.github/workflows/base64ct.yml +++ b/.github/workflows/inout.yml @@ -1,16 +1,16 @@ -name: base64ct +name: inout on: pull_request: - paths: - - "base64ct/**" - - "Cargo.*" + paths: + - "inout/**" + - "Cargo.*" push: branches: master defaults: run: - working-directory: base64ct + working-directory: inout env: CARGO_INCREMENTAL: 0 @@ -22,36 +22,43 @@ jobs: strategy: matrix: rust: - - 1.47.0 # MSRV + - 1.56.0 # MSRV - stable target: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} override: true - - run: cargo build --target ${{ matrix.target }} --release - - run: cargo build --target ${{ matrix.target }} --release --features alloc + - run: cargo build --target ${{ matrix.target }} + - run: cargo build --features block-padding --target ${{ matrix.target }} + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} test: runs-on: ubuntu-latest strategy: matrix: rust: - - 1.47.0 # MSRV + - 1.56.0 # MSRV - stable steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.rust }} override: true - - run: cargo test --release - - run: cargo test --release --features alloc - - run: cargo test --release --all-features + - run: cargo test + - run: cargo test --features block-padding + - run: cargo test --all-features diff --git a/.github/workflows/opaque-debug.yml b/.github/workflows/opaque-debug.yml index 8493e54b..09955d71 100644 --- a/.github/workflows/opaque-debug.yml +++ b/.github/workflows/opaque-debug.yml @@ -28,7 +28,8 @@ jobs: - thumbv7em-none-eabi - wasm32-unknown-unknown steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -36,6 +37,12 @@ jobs: target: ${{ matrix.target }} override: true - run: cargo build --release --target ${{ matrix.target }} + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + test: runs-on: ubuntu-latest strategy: @@ -44,10 +51,11 @@ jobs: - 1.41.0 # MSRV - stable steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo test --release diff --git a/.github/workflows/pem-rfc7468.yml b/.github/workflows/pem-rfc7468.yml deleted file mode 100644 index b65bf862..00000000 --- a/.github/workflows/pem-rfc7468.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: pem-rfc7468 - -on: - pull_request: - paths: - - "base64ct/**" - - "pem-rfc7468/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: pem-rfc7468 - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --target ${{ matrix.target }} --release - - run: cargo build --target ${{ matrix.target }} --release --features alloc - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release --no-default-features - - run: cargo test --release - - run: cargo test --release --all-features diff --git a/.github/workflows/pkcs1.yml b/.github/workflows/pkcs1.yml deleted file mode 100644 index dd922015..00000000 --- a/.github/workflows/pkcs1.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: pkcs1 - -on: - pull_request: - paths: - - "base64ct/**" - - "const-oid/**" - - "der/**" - - "pkcs1/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: pkcs1 - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --target ${{ matrix.target }} --release --no-default-features - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release --no-default-features - - run: cargo test --release - - run: cargo test --release --all-features diff --git a/.github/workflows/pkcs5.yml b/.github/workflows/pkcs5.yml deleted file mode 100644 index 6ef470a5..00000000 --- a/.github/workflows/pkcs5.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: pkcs5 - -on: - pull_request: - paths: - - "const-oid/**" - - "der/**" - - "pkcs5/**" - - "spki/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: pkcs5 - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --target ${{ matrix.target }} --release - - run: cargo build --target ${{ matrix.target }} --release --features alloc - - run: cargo build --target ${{ matrix.target }} --release --features pbes2 - - run: cargo build --target ${{ matrix.target }} --release --features scrypt - - run: cargo build --target ${{ matrix.target }} --release --features alloc,pbes2 - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release - - run: cargo test --release --features alloc - - run: cargo test --release --features pbes2 - - run: cargo test --release --features scrypt - - run: cargo test --release --all-features diff --git a/.github/workflows/pkcs8.yml b/.github/workflows/pkcs8.yml deleted file mode 100644 index 07c1eb3d..00000000 --- a/.github/workflows/pkcs8.yml +++ /dev/null @@ -1,71 +0,0 @@ -name: pkcs8 - -on: - pull_request: - paths: - - "base64ct/**" - - "const-oid/**" - - "der/**" - - "pkcs8/**" - - "spki/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: pkcs8 - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --release --target ${{ matrix.target }} --no-default-features - - run: cargo build --release --target ${{ matrix.target }} --no-default-features --features alloc - - run: cargo build --release --target ${{ matrix.target }} --no-default-features --features encryption - - run: cargo build --release --target ${{ matrix.target }} --no-default-features --features pem - - run: cargo build --release --target ${{ matrix.target }} --no-default-features --features pkcs1 - - run: cargo build --release --target ${{ matrix.target }} --no-default-features --features pkcs5 - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release --no-default-features - - run: cargo test --release - - run: cargo test --release --features alloc - - run: cargo test --release --features encryption - - run: cargo test --release --features pem - - run: cargo test --release --features pkcs1 - - run: cargo test --release --features pkcs1,alloc - - run: cargo test --release --features pkcs5 - - run: cargo test --release --all-features diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 0d82d54b..4042a37d 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -13,7 +13,7 @@ jobs: name: Security Audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Cache cargo bin uses: actions/cache@v1 with: diff --git a/.github/workflows/spki.yml b/.github/workflows/spki.yml deleted file mode 100644 index e7b62901..00000000 --- a/.github/workflows/spki.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: spki - -on: - pull_request: - paths: - - "base64ct/**" - - "const-oid/**" - - "der/**" - - "spki/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: spki - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --release --target ${{ matrix.target }} - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release - - run: cargo test --release --all-features diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index f954ce25..2f230a9d 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -13,19 +13,20 @@ jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.51.0 # Highest MSRV in repo - components: clippy - override: true - profile: minimal - - run: cargo clippy --all --all-features -- -D warnings + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.59.0 # Highest MSRV in repo + components: clippy + override: true + profile: minimal + - run: cargo clippy --all --all-features -- -D warnings rustfmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: stable diff --git a/.github/workflows/x509.yml b/.github/workflows/x509.yml deleted file mode 100644 index 75b8d932..00000000 --- a/.github/workflows/x509.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: x509 - -on: - pull_request: - paths: - - "der/**" - - "spki/**" - - "x509/**" - - "Cargo.*" - push: - branches: master - -defaults: - run: - working-directory: x509 - -env: - CARGO_INCREMENTAL: 0 - RUSTFLAGS: "-Dwarnings" - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - target: - - thumbv7em-none-eabi - - wasm32-unknown-unknown - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - run: cargo build --release --target ${{ matrix.target }} - - test: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - 1.51.0 # MSRV - - stable - steps: - - uses: actions/checkout@v1 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - run: cargo test --release - - run: cargo test --release --all-features diff --git a/.github/workflows/zeroize.yml b/.github/workflows/zeroize.yml new file mode 100644 index 00000000..b9fbbee6 --- /dev/null +++ b/.github/workflows/zeroize.yml @@ -0,0 +1,89 @@ +name: zeroize + +on: + pull_request: + paths: + - "zeroize/**" + - "Cargo.*" + push: + branches: main + +defaults: + run: + working-directory: zeroize + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.51.0 # MSRV + - stable + target: + - armv7a-none-eabi + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + profile: minimal + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} + + test: + strategy: + matrix: + platform: + - ubuntu-latest + - macos-latest + - windows-latest + toolchain: + - 1.51.0 # MSRV + - stable + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + override: true + profile: minimal + - run: cargo test --release + - run: cargo test --release --features alloc,derive + + # Feature-gated ARM64 SIMD register support (nightly-only) + aarch64: + strategy: + matrix: + include: + - target: aarch64-unknown-linux-gnu + rust: nightly-2022-03-01 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: RustCrypto/actions/cargo-cache@master + - run: ${{ matrix.deps }} + - uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + profile: minimal + override: true + - uses: RustCrypto/actions/cross-install@master + - run: cross test --release --target ${{ matrix.target }} --features aarch64 + - run: cross test --release --target ${{ matrix.target }} --all-features diff --git a/Cargo.lock b/Cargo.lock index 71b2079e..22e58439 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,174 +2,47 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aes" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495ee669413bfbe9e8cace80f4d3d78e6d8c8d99579f97fb93bde351b185f2d4" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures 0.1.4", - "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "base64ct" -version = "1.0.1" - [[package]] name = "blobby" -version = "0.3.0" +version = "0.3.1" dependencies = [ "hex", ] [[package]] name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +version = "0.10.2" dependencies = [ "generic-array", ] [[package]] -name = "block-buffer" -version = "0.10.0-pre.4" -dependencies = [ - "block-padding 0.3.0-pre", - "generic-array", -] - -[[package]] -name = "block-modes" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" -dependencies = [ - "block-padding 0.2.1", - "cipher", -] - -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - -[[package]] -name = "block-padding" -version = "0.3.0-pre" -dependencies = [ - "generic-array", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array", -] +name = "cmov" +version = "0.1.1" [[package]] name = "collectable" version = "0.0.2" -[[package]] -name = "const-oid" -version = "0.6.0" -dependencies = [ - "hex-literal 0.3.1", -] - -[[package]] -name = "cpufeatures" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8" -dependencies = [ - "libc", -] - [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" dependencies = [ "libc", ] -[[package]] -name = "crypto-bigint" -version = "0.2.4" -dependencies = [ - "generic-array", - "hex-literal 0.3.1", - "rand_chacha", - "rand_core", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-mac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "dbl" -version = "0.3.1" -dependencies = [ - "generic-array", -] - -[[package]] -name = "der" -version = "0.4.1" -dependencies = [ - "const-oid", - "crypto-bigint", - "der_derive", - "hex-literal 0.3.1", -] - -[[package]] -name = "der_derive" -version = "0.4.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +version = "0.3.2" dependencies = [ "generic-array", ] [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", @@ -183,182 +56,62 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5af1f635ef1bc545d78392b136bfe1c9809e029023c84a3638a864a10b8819c8" - -[[package]] -name = "hex-literal" -version = "0.3.3" - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac", - "digest", -] +version = "0.3.4" [[package]] name = "itoa" -version = "0.4.7" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "libc" -version = "0.2.101" +version = "0.2.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" [[package]] name = "opaque-debug" version = "0.3.0" -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "pbkdf2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" -dependencies = [ - "crypto-mac", -] - -[[package]] -name = "pem-rfc7468" -version = "0.2.0" -dependencies = [ - "base64ct", -] - -[[package]] -name = "pkcs1" -version = "0.2.3" -dependencies = [ - "der", - "hex-literal 0.3.1", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "pkcs5" -version = "0.3.0" -dependencies = [ - "aes", - "block-modes", - "der", - "hex-literal 0.3.1", - "hmac", - "pbkdf2", - "scrypt", - "sha2", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.7.5" -dependencies = [ - "der", - "hex-literal 0.3.1", - "pem-rfc7468", - "pkcs1", - "pkcs5", - "rand_core", - "spki", - "zeroize", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" - [[package]] name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - -[[package]] -name = "salsa20" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7c5f10864beba947e1a1b43f3ef46c8cc58d1c2ae549fa471713e8ff60787a" -dependencies = [ - "cipher", -] - -[[package]] -name = "scrypt" -version = "0.7.0" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879588d8f90906e73302547e20fffefdd240eb3e0e744e142321f5d49dea0518" -dependencies = [ - "hmac", - "pbkdf2", - "salsa20", - "sha2", -] +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "serde" -version = "1.0.129" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.129" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -367,46 +120,20 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.66" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "itoa", "ryu", "serde", ] -[[package]] -name = "sha2" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures 0.1.4", - "digest", - "opaque-debug 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "spki" -version = "0.4.0" -dependencies = [ - "der", -] - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - [[package]] name = "syn" -version = "1.0.75" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", @@ -415,9 +142,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", @@ -427,21 +154,21 @@ dependencies = [ [[package]] name = "typenum" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wycheproof2blb" @@ -454,15 +181,18 @@ dependencies = [ ] [[package]] -name = "x509" -version = "0.0.0" +name = "zeroize" +version = "1.5.4" dependencies = [ - "der", - "spki", + "zeroize_derive", ] [[package]] -name = "zeroize" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd" +name = "zeroize_derive" +version = "1.3.2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] diff --git a/Cargo.toml b/Cargo.toml index a87631eb..1f3a386b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,14 @@ [workspace] members = [ - "base64ct", "blobby", "block-buffer", - "block-padding", "collectable", - "const-oid", + "cmov", "cpufeatures", - "crypto-bigint", "dbl", - "der", - "der/derive", "hex-literal", "opaque-debug", - "pem-rfc7468", - "pkcs1", - "pkcs5", - "pkcs8", - "spki", "wycheproof2blb", - "x509" + "zeroize", + "zeroize/derive" ] diff --git a/README.md b/README.md index 468acdcc..856d52d0 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,27 @@ -# RustCrypto: Utilities [![Project Chat][chat-image]][chat-link] [![dependency status][deps-image]][deps-link] +# RustCrypto: Utilities + +[![Project Chat][chat-image]][chat-link] +[![dependency status][deps-image]][deps-link] +![Apache2/MIT licensed][license-image] This repository contains various utility crates used in the RustCrypto project. ## Crates -| Name | crates.io | Docs | Description | -|------|-----------|------|--------------| -| `base64ct` | [![crates.io](https://img.shields.io/crates/v/base64ct.svg)](https://crates.io/crates/base64ct) | [![Documentation](https://docs.rs/base64ct/badge.svg)](https://docs.rs/base64ct) | Constant-time encoder and decoder of several Base64 variants | -| `blobby` | [![crates.io](https://img.shields.io/crates/v/blobby.svg)](https://crates.io/crates/blobby) | [![Documentation](https://docs.rs/blobby/badge.svg)](https://docs.rs/blobby) | Decoder of the simple de-duplicated binary blob storage format | -| `block-buffer` | [![crates.io](https://img.shields.io/crates/v/block-buffer.svg)](https://crates.io/crates/block-buffer) | [![Documentation](https://docs.rs/block-buffer/badge.svg)](https://docs.rs/block-buffer) | Fixed size buffer for block processing of data | -| `block‑padding` | [![crates.io](https://img.shields.io/crates/v/block-padding.svg)](https://crates.io/crates/block-padding) | [![Documentation](https://docs.rs/block-padding/badge.svg)](https://docs.rs/block-padding) | Padding and unpadding of messages divided into blocks | -| `collectable` | [![crates.io](https://img.shields.io/crates/v/collectable.svg)](https://crates.io/crates/collectable) | [![Documentation](https://docs.rs/collectable/badge.svg)](https://docs.rs/collectable) | Fallible, `no_std`-friendly collection traits | -| `const-oid` | [![crates.io](https://img.shields.io/crates/v/const-oid.svg)](https://crates.io/crates/const-oid) | [![Documentation](https://docs.rs/const-oid/badge.svg)](https://docs.rs/const-oid) | Const-friendly implementation of the ISO/IEC Object Identifier (OID) standard as defined in [ITU X.660] | -| `cpufeatures` | [![crates.io](https://img.shields.io/crates/v/cpufeatures.svg)](https://crates.io/crates/cpufeatures) | [![Documentation](https://docs.rs/cpufeatures/badge.svg)](https://docs.rs/cpufeatures) | Lightweight and efficient alternative to the `is_x86_feature_detected!` macro | -| `crypto‑bigint` | [![crates.io](https://img.shields.io/crates/v/crypto-bigint.svg)](https://crates.io/crates/crypto-bigint) | [![Documentation](https://docs.rs/crypto-bigint/badge.svg)](https://docs.rs/crypto-bigint) | Big integer library for cryptographic applciations | -| `dbl` | [![crates.io](https://img.shields.io/crates/v/dbl.svg)](https://crates.io/crates/dbl) | [![Documentation](https://docs.rs/dbl/badge.svg)](https://docs.rs/dbl) | Double operation in Galois Field (GF) | -| `der` | [![crates.io](https://img.shields.io/crates/v/der.svg)](https://crates.io/crates/der) | [![Documentation](https://docs.rs/der/badge.svg)](https://docs.rs/der) | Decoder and encoder of the Distinguished Encoding Rules (DER) for Abstract Syntax Notation One (ASN.1) as described in [ITU X.690] | -| `hex-literal` | [![crates.io](https://img.shields.io/crates/v/hex-literal.svg)](https://crates.io/crates/hex-literal) | [![Documentation](https://docs.rs/hex-literal/badge.svg)](https://docs.rs/hex-literal) | Procedural macro for converting hexadecimal string to byte array at compile time | -| `opaque-debug` | [![crates.io](https://img.shields.io/crates/v/opaque-debug.svg)](https://crates.io/crates/opaque-debug) | [![Documentation](https://docs.rs/opaque-debug/badge.svg)](https://docs.rs/opaque-debug) | Macro for opaque `Debug` trait implementation | -| `pem-rfc7468` | [![crates.io](https://img.shields.io/crates/v/pem-rfc7468.svg)](https://crates.io/crates/pem-rfc7468) | [![Documentation](https://docs.rs/pem-rfc7468/badge.svg)](https://docs.rs/pem-rfc7468) | Strict PEM encoding for PKIX/PKCS/CMS objects | -| `pkcs1` | [![crates.io](https://img.shields.io/crates/v/pkcs1.svg)](https://crates.io/crates/pkcs1) | [![Documentation](https://docs.rs/pkcs1/badge.svg)](https://docs.rs/pkcs1) | Implementation of PKCS#1: RSA Cryptography Specifications Version 2.2 ([RFC 8017]) | -| `pkcs5` | [![crates.io](https://img.shields.io/crates/v/pkcs5.svg)](https://crates.io/crates/pkcs5) | [![Documentation](https://docs.rs/pkcs5/badge.svg)](https://docs.rs/pkcs5) | Implementation of PKCS#5: Password-Based Cryptography Specification Version 2.1 ([RFC 8018]) | -| `pkcs8` | [![crates.io](https://img.shields.io/crates/v/pkcs8.svg)](https://crates.io/crates/pkcs8) | [![Documentation](https://docs.rs/pkcs8/badge.svg)](https://docs.rs/pkcs8) | Implementation of PKCS#8(v2): Private-Key Information Syntax Specification ([RFC 5208]) and asymmetric key packages ([RFC 5958]) | -| `spki` | [![crates.io](https://img.shields.io/crates/v/spki.svg)](https://crates.io/crates/spki) | [![Documentation](https://docs.rs/spki/badge.svg)](https://docs.rs/spki) | X.509 Subject Public Key Info ([RFC 5280 Section 4.1]) describing public keys as well as their associated AlgorithmIdentifiers (i.e. OIDs) | -| `x509` | [![crates.io](https://img.shields.io/crates/v/x509.svg)](https://crates.io/crates/x509) | [![Documentation](https://docs.rs/x509/badge.svg)](https://docs.rs/x509) | Implementation of the X.509 Public Key Infrastructure Certificate format as described in [RFC 5280] | +| Name | crates.io | Docs | MSRV | Description | +|------|:---------:|:----:|:----:|-------------| +| [`blobby`] | [![crates.io](https://img.shields.io/crates/v/blobby.svg)](https://crates.io/crates/blobby) | [![Documentation](https://docs.rs/blobby/badge.svg)](https://docs.rs/blobby) | ![MSRV 1.39][msrv-1.39] | Decoder of the simple de-duplicated binary blob storage format | +| [`block-buffer`] | [![crates.io](https://img.shields.io/crates/v/block-buffer.svg)](https://crates.io/crates/block-buffer) | [![Documentation](https://docs.rs/block-buffer/badge.svg)](https://docs.rs/block-buffer) | ![MSRV 1.41][msrv-1.41] | Fixed size buffer for block processing of data | +| [`block‑padding`] | [![crates.io](https://img.shields.io/crates/v/block-padding.svg)](https://crates.io/crates/block-padding) | [![Documentation](https://docs.rs/block-padding/badge.svg)](https://docs.rs/block-padding) | ![MSRV 1.56][msrv-1.56] | Padding and unpadding of messages divided into blocks | +| [`cmov`] | [![crates.io](https://img.shields.io/crates/v/cmov.svg)](https://crates.io/crates/cmov) | [![Documentation](https://docs.rs/cmov/badge.svg)](https://docs.rs/cmov) | ![MSRV 1.59][msrv-1.59] | Conditional move intrinsics | +| [`collectable`] | [![crates.io](https://img.shields.io/crates/v/collectable.svg)](https://crates.io/crates/collectable) | [![Documentation](https://docs.rs/collectable/badge.svg)](https://docs.rs/collectable) | ![MSRV 1.41][msrv-1.41] | Fallible, `no_std`-friendly collection traits | +| [`cpufeatures`] | [![crates.io](https://img.shields.io/crates/v/cpufeatures.svg)](https://crates.io/crates/cpufeatures) | [![Documentation](https://docs.rs/cpufeatures/badge.svg)](https://docs.rs/cpufeatures) | ![MSRV 1.40][msrv-1.40] | Lightweight and efficient alternative to the `is_x86_feature_detected!` macro | +| [`dbl`] | [![crates.io](https://img.shields.io/crates/v/dbl.svg)](https://crates.io/crates/dbl) | [![Documentation](https://docs.rs/dbl/badge.svg)](https://docs.rs/dbl) | ![MSRV 1.41][msrv-1.41] | Double operation in Galois Field (GF) | +| [`hex-literal`] | [![crates.io](https://img.shields.io/crates/v/hex-literal.svg)](https://crates.io/crates/hex-literal) | [![Documentation](https://docs.rs/hex-literal/badge.svg)](https://docs.rs/hex-literal) | ![MSRV 1.45][msrv-1.45] | Procedural macro for converting hexadecimal string to byte array at compile time | +| [`inout`] | [![crates.io](https://img.shields.io/crates/v/inout.svg)](https://crates.io/crates/inout) | [![Documentation](https://docs.rs/inout/badge.svg)](https://docs.rs/inout) | ![MSRV 1.56][msrv-1.56] | Custom reference types for code generic over in-place and buffer-to-buffer modes of operation. | +| [`opaque-debug`] | [![crates.io](https://img.shields.io/crates/v/opaque-debug.svg)](https://crates.io/crates/opaque-debug) | [![Documentation](https://docs.rs/opaque-debug/badge.svg)](https://docs.rs/opaque-debug) | ![MSRV 1.41][msrv-1.41] | Macro for opaque `Debug` trait implementation | +| [`wycheproof2blb`] | | | | Utility for converting [Wycheproof] test vectors to the blobby format | +| [`zeroize`] | [![crates.io](https://img.shields.io/crates/v/zeroize.svg)](https://crates.io/crates/zeroize) | [![Documentation](https://docs.rs/zeroize/badge.svg)](https://docs.rs/zeroize) | ![MSRV 1.51][msrv-1.51] | Securely zero memory while avoiding compiler optimizations | ## License @@ -36,22 +34,39 @@ at your option. ### Contribution -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [//]: # (badges) [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [deps-image]: https://deps.rs/repo/github/RustCrypto/utils/status.svg [deps-link]: https://deps.rs/repo/github/RustCrypto/utils -[ITU X.660]: https://www.itu.int/rec/T-REC-X.660 -[ITU X.690]: https://www.itu.int/rec/T-REC-X.690 -[RFC 8017]: https://datatracker.ietf.org/doc/html/rfc8017 -[RFC 8018]: https://datatracker.ietf.org/doc/html/rfc8018 -[RFC 5208]: https://datatracker.ietf.org/doc/html/rfc5208 -[RFC 5958]: https://datatracker.ietf.org/doc/html/rfc5958 -[RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 -[RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280 +[msrv-1.39]: https://img.shields.io/badge/rustc-1.39.0+-blue.svg +[msrv-1.40]: https://img.shields.io/badge/rustc-1.40.0+-blue.svg +[msrv-1.41]: https://img.shields.io/badge/rustc-1.41.0+-blue.svg +[msrv-1.45]: https://img.shields.io/badge/rustc-1.45.0+-blue.svg +[msrv-1.51]: https://img.shields.io/badge/rustc-1.51.0+-blue.svg +[msrv-1.56]: https://img.shields.io/badge/rustc-1.56.0+-blue.svg +[msrv-1.59]: https://img.shields.io/badge/rustc-1.59.0+-blue.svg + +[//]: # (crates) + +[`blobby`]: ./blobby +[`block-buffer`]: ./block-buffer +[`block‑padding`]: ./block-padding +[`cmov`]: ./cmov +[`collectable`]: ./collectable +[`cpufeatures`]: ./cpufeatures +[`dbl`]: ./dbl +[`hex-literal`]: ./hex-literal +[`inout`]: ./inout +[`opaque-debug`]: ./opaque-debug +[`wycheproof2blb`]: ./wycheproof2blb +[`zeroize`]: ./zeroize + +[//]: # (misc) + +[Wycheproof]: https://github.com/google/wycheproof diff --git a/base64ct/CHANGELOG.md b/base64ct/CHANGELOG.md deleted file mode 100644 index fd0c76d7..00000000 --- a/base64ct/CHANGELOG.md +++ /dev/null @@ -1,56 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 1.0.1 (2021-08-14) -### Fixed -- Make `Encoding::decode` reject invalid padding ([#577]) - -[#577]: https://github.com/RustCrypto/utils/pull/577 - -## 1.0.0 (2021-03-17) -### Changed -- Bump MSRV to 1.47+ ([#334]) - -### Fixed -- MSRV-dependent TODOs in implementation ([#334]) - -[#334]: https://github.com/RustCrypto/utils/pull/334 - -## 0.2.1 (2021-03-07) -### Fixed -- MSRV docs ([#328]) - -[#328]: https://github.com/RustCrypto/utils/pull/328 - -## 0.2.0 (2021-02-01) -### Changed -- Refactor with `Encoding` trait ([#238]) -- Internal refactoring ([#241], [#242]) - -[#238]: https://github.com/RustCrypto/utils/pull/238 -[#241]: https://github.com/RustCrypto/utils/pull/241 -[#242]: https://github.com/RustCrypto/utils/pull/242 - -## 0.1.2 (2021-01-31) -### Added -- bcrypt encoding ([#237]) -- `crypt(3)` encoding ([#239]) - -### Changed -- Internal refactoring ([#235], [#236]) - -[#235]: https://github.com/RustCrypto/utils/pull/235 -[#236]: https://github.com/RustCrypto/utils/pull/236 -[#237]: https://github.com/RustCrypto/utils/pull/237 -[#239]: https://github.com/RustCrypto/utils/pull/239 - -## 0.1.1 (2021-01-27) -- Minor code improvements ([#234]) - -[#234]: https://github.com/RustCrypto/utils/pull/234 - -## 0.1.0 (2021-01-26) -- Initial release diff --git a/base64ct/Cargo.toml b/base64ct/Cargo.toml deleted file mode 100644 index 1c12e683..00000000 --- a/base64ct/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "base64ct" -version = "1.0.1" # Also update html_root_url in lib.rs when bumping this -description = """ -Pure Rust implementation of Base64 (RFC 4648) which avoids any usages of -data-dependent branches/LUTs and thereby provides portable "best effort" -constant-time operation and embedded-friendly no_std support -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -documentation = "https://docs.rs/pkcs8" -repository = "https://github.com/RustCrypto/utils/tree/master/base64ct" -categories = ["cryptography", "encoding", "no-std", "parser-implementations"] -keywords = ["base64", "phc"] -readme = "README.md" - -[features] -alloc = [] -std = ["alloc"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/base64ct/LICENSE-MIT b/base64ct/LICENSE-MIT deleted file mode 100644 index da278e68..00000000 --- a/base64ct/LICENSE-MIT +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com) -Copyright (c) 2021 The RustCrypto Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/base64ct/README.md b/base64ct/README.md deleted file mode 100644 index d395839f..00000000 --- a/base64ct/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# RustCrypto: Constant-Time Base64 - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Pure Rust implementation of Base64 ([RFC 4648]). - -Implements multiple Base64 variants without data-dependent branches or lookup -tables, thereby providing portable "best effort" constant-time operation. - -Supports `no_std` environments and avoids heap allocations in the core API -(but also provides optional `alloc` support for convenience). - -[Documentation][docs-link] - -## Supported Base64 variants - -- Standard Base64: `[A-Z]`, `[a-z]`, `[0-9]`, `+`, `/` -- URL-safe Base64: `[A-Z]`, `[a-z]`, `[0-9]`, `-`, `_` -- bcrypt Base64: `.`, `/`, `[A-Z]`, `[a-z]`, `[0-9]` -- `crypt(3)` Base64: `.`, `-`, `[0-9]`, `[A-Z]`, `[a-z]` - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/base64ct.svg -[crate-link]: https://crates.io/crates/base64ct -[docs-image]: https://docs.rs/base64ct/badge.svg -[docs-link]: https://docs.rs/base64ct/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.47+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/base64ct/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions?query=workflow:base64ct - -[//]: # (general links) - -[RFC 4648]: https://tools.ietf.org/html/rfc4648 diff --git a/base64ct/benches/mod.rs b/base64ct/benches/mod.rs deleted file mode 100644 index 89116a7e..00000000 --- a/base64ct/benches/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -#![feature(test)] -extern crate test; -use test::Bencher; - -const B64_LEN: usize = 100_002; -const RAW_LEN: usize = (3 * B64_LEN) / 4; - -#[inline(never)] -fn get_raw_data() -> Vec { - (0..RAW_LEN).map(|i| i as u8).collect() -} - -#[inline(never)] -fn get_b64_data() -> String { - (0..B64_LEN) - .map(|i| match (i % 64) as u8 { - v @ 0..=25 => (v + 'A' as u8) as char, - v @ 26..=51 => (v - 26 + 'a' as u8) as char, - v @ 52..=61 => (v - 52 + '0' as u8) as char, - 62 => '+', - _ => '/', - }) - .collect() -} - -#[bench] -fn decode_bench(b: &mut Bencher) { - let b64_data = get_b64_data(); - let mut buf = get_raw_data(); - b.iter(|| { - let out = base64ct::decode(&b64_data, &mut buf).unwrap(); - test::black_box(out); - }); - b.bytes = RAW_LEN as u64; -} - -#[bench] -fn decode_in_place_bench(b: &mut Bencher) { - let mut b64_data = get_b64_data().into_bytes(); - b.iter(|| { - // since it works on the same buffer over and over, - // almost always `out` will be an error - let out = base64ct::decode_in_place(&mut b64_data); - let _ = test::black_box(out); - }); - b.bytes = RAW_LEN as u64; -} - -#[bench] -fn encode_bench(b: &mut Bencher) { - let mut buf = get_b64_data().into_bytes(); - let raw_data = get_raw_data(); - b.iter(|| { - let out = base64ct::encode(&raw_data, &mut buf).unwrap(); - test::black_box(out); - }); - b.bytes = RAW_LEN as u64; -} diff --git a/base64ct/src/encoding.rs b/base64ct/src/encoding.rs deleted file mode 100644 index 911b2d40..00000000 --- a/base64ct/src/encoding.rs +++ /dev/null @@ -1,348 +0,0 @@ -//! Base64 encodings - -use crate::{ - errors::{Error, InvalidEncodingError, InvalidLengthError}, - variant::Variant, -}; -use core::str; - -#[cfg(feature = "alloc")] -use alloc::{string::String, vec::Vec}; - -/// Padding character -const PAD: u8 = b'='; - -/// Base64 encoding trait. -/// -/// This trait must be imported to make use of any Base64 variant defined -/// in this crate. -pub trait Encoding { - /// Decode a Base64 string into the provided destination buffer. - fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error>; - - /// Decode a Base64 string in-place. - /// - /// NOTE: this method does not (yet) validate that padding is well-formed, - /// if the given Base64 encoding is padded. - fn decode_in_place(buf: &mut [u8]) -> Result<&[u8], InvalidEncodingError>; - - /// Decode a Base64 string into a byte vector. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn decode_vec(input: &str) -> Result, Error>; - - /// Encode the input byte slice as Base64. - /// - /// Writes the result into the provided destination slice, returning an - /// ASCII-encoded Base64 string value. - fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, InvalidLengthError>; - - /// Encode input byte slice into a [`String`] containing Base64. - /// - /// # Panics - /// If `input` length is greater than `usize::MAX/4`. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn encode_string(input: &[u8]) -> String; - - /// Get the length of Base64 produced by encoding the given bytes. - /// - /// WARNING: this function will return `0` for lengths greater than `usize::MAX/4`! - fn encoded_len(bytes: &[u8]) -> usize; -} - -impl Encoding for T { - fn decode(src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], Error> { - let (src_unpadded, mut err) = if T::PADDED { - let (unpadded_len, e) = decode_padding(src.as_ref())?; - (&src.as_ref()[..unpadded_len], e) - } else { - (src.as_ref(), 0) - }; - - let dlen = decoded_len(src_unpadded.len()); - - if dlen > dst.len() { - return Err(Error::InvalidLength); - } - - let dst = &mut dst[..dlen]; - - let mut src_chunks = src_unpadded.chunks_exact(4); - let mut dst_chunks = dst.chunks_exact_mut(3); - for (s, d) in (&mut src_chunks).zip(&mut dst_chunks) { - err |= Self::decode_3bytes(s, d); - } - let src_rem = src_chunks.remainder(); - let dst_rem = dst_chunks.into_remainder(); - - err |= !(src_rem.is_empty() || src_rem.len() >= 2) as i16; - let mut tmp_out = [0u8; 3]; - let mut tmp_in = [b'A'; 4]; - tmp_in[..src_rem.len()].copy_from_slice(src_rem); - err |= Self::decode_3bytes(&tmp_in, &mut tmp_out); - dst_rem.copy_from_slice(&tmp_out[..dst_rem.len()]); - - if err == 0 { - validate_padding::(src.as_ref(), dst)?; - Ok(dst) - } else { - Err(Error::InvalidEncoding) - } - } - - fn decode_in_place(mut buf: &mut [u8]) -> Result<&[u8], InvalidEncodingError> { - // TODO: eliminate unsafe code when LLVM12 is stable - // See: https://github.com/rust-lang/rust/issues/80963 - let mut err = if T::PADDED { - let (unpadded_len, e) = decode_padding(buf)?; - buf = &mut buf[..unpadded_len]; - e - } else { - 0 - }; - - let dlen = decoded_len(buf.len()); - let full_chunks = buf.len() / 4; - - for chunk in 0..full_chunks { - // SAFETY: `p3` and `p4` point inside `buf`, while they may overlap, - // read and write are clearly separated from each other and done via - // raw pointers. - unsafe { - debug_assert!(3 * chunk + 3 <= buf.len()); - debug_assert!(4 * chunk + 4 <= buf.len()); - - let p3 = buf.as_mut_ptr().add(3 * chunk) as *mut [u8; 3]; - let p4 = buf.as_ptr().add(4 * chunk) as *const [u8; 4]; - - let mut tmp_out = [0u8; 3]; - err |= Self::decode_3bytes(&*p4, &mut tmp_out); - *p3 = tmp_out; - } - } - - let src_rem_pos = 4 * full_chunks; - let src_rem_len = buf.len() - src_rem_pos; - let dst_rem_pos = 3 * full_chunks; - let dst_rem_len = dlen - dst_rem_pos; - - err |= !(src_rem_len == 0 || src_rem_len >= 2) as i16; - let mut tmp_in = [b'A'; 4]; - tmp_in[..src_rem_len].copy_from_slice(&buf[src_rem_pos..]); - let mut tmp_out = [0u8; 3]; - - err |= Self::decode_3bytes(&tmp_in, &mut tmp_out); - - if err == 0 { - // SAFETY: `dst_rem_len` is always smaller than 4, so we don't - // read outside of `tmp_out`, write and the final slicing never go - // outside of `buf`. - unsafe { - debug_assert!(dst_rem_pos + dst_rem_len <= buf.len()); - debug_assert!(dst_rem_len <= tmp_out.len()); - debug_assert!(dlen <= buf.len()); - - core::ptr::copy_nonoverlapping( - tmp_out.as_ptr(), - buf.as_mut_ptr().add(dst_rem_pos), - dst_rem_len, - ); - Ok(buf.get_unchecked(..dlen)) - } - } else { - Err(InvalidEncodingError) - } - } - - #[cfg(feature = "alloc")] - fn decode_vec(input: &str) -> Result, Error> { - let mut output = vec![0u8; decoded_len(input.len())]; - let len = Self::decode(input, &mut output)?.len(); - - if len <= output.len() { - output.truncate(len); - Ok(output) - } else { - Err(Error::InvalidLength) - } - } - - fn encode<'a>(src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, InvalidLengthError> { - let elen = match encoded_len_inner(src.len(), T::PADDED) { - Some(v) => v, - None => return Err(InvalidLengthError), - }; - - if elen > dst.len() { - return Err(InvalidLengthError); - } - - let dst = &mut dst[..elen]; - - let mut src_chunks = src.chunks_exact(3); - let mut dst_chunks = dst.chunks_exact_mut(4); - - for (s, d) in (&mut src_chunks).zip(&mut dst_chunks) { - Self::encode_3bytes(s, d); - } - - let src_rem = src_chunks.remainder(); - - if T::PADDED { - if let Some(dst_rem) = dst_chunks.next() { - let mut tmp = [0u8; 3]; - tmp[..src_rem.len()].copy_from_slice(src_rem); - Self::encode_3bytes(&tmp, dst_rem); - - let flag = src_rem.len() == 1; - let mask = (flag as u8).wrapping_sub(1); - dst_rem[2] = (dst_rem[2] & mask) | (PAD & !mask); - dst_rem[3] = PAD; - } - } else { - let dst_rem = dst_chunks.into_remainder(); - - let mut tmp_in = [0u8; 3]; - let mut tmp_out = [0u8; 4]; - tmp_in[..src_rem.len()].copy_from_slice(src_rem); - Self::encode_3bytes(&tmp_in, &mut tmp_out); - dst_rem.copy_from_slice(&tmp_out[..dst_rem.len()]); - } - - debug_assert!(str::from_utf8(dst).is_ok()); - - // SAFETY: values written by `encode_3bytes` are valid one-byte UTF-8 chars - Ok(unsafe { str::from_utf8_unchecked(dst) }) - } - - #[cfg(feature = "alloc")] - fn encode_string(input: &[u8]) -> String { - let elen = encoded_len_inner(input.len(), T::PADDED).expect("input is too big"); - let mut dst = vec![0u8; elen]; - let res = Self::encode(input, &mut dst).expect("encoding error"); - - debug_assert_eq!(elen, res.len()); - debug_assert!(str::from_utf8(&dst).is_ok()); - - // SAFETY: `dst` is fully written and contains only valid one-byte UTF-8 chars - unsafe { String::from_utf8_unchecked(dst) } - } - - fn encoded_len(bytes: &[u8]) -> usize { - encoded_len_inner(bytes.len(), T::PADDED).unwrap_or(0) - } -} - -/// Get the length of the output from decoding the provided *unpadded* -/// Base64-encoded input (use [`unpadded_len_ct`] to compute this value for -/// a padded input) -/// -/// Note that this function does not fully validate the Base64 is well-formed -/// and may return incorrect results for malformed Base64. -#[inline(always)] -fn decoded_len(input_len: usize) -> usize { - // overflow-proof computation of `(3*n)/4` - let k = input_len / 4; - let l = input_len - 4 * k; - 3 * k + (3 * l) / 4 -} - -/// Validate padding is of the expected length compute unpadded length. -/// -/// Note that this method does not explicitly check that the padded data -/// is valid in and of itself: that is performed by `validate_padding` as a -/// final step. -/// -/// Returns length-related errors eagerly as a [`Result`], and data-dependent -/// errors (i.e. malformed padding bytes) as `i16` to be combined with other -/// encoding-related errors prior to branching. -#[inline(always)] -fn decode_padding(input: &[u8]) -> Result<(usize, i16), InvalidEncodingError> { - if input.len() % 4 != 0 { - return Err(InvalidEncodingError); - } - - let unpadded_len = match *input { - [.., b0, b1] => { - let pad_len = is_pad_ct(b0) + is_pad_ct(b1); - input.len() - pad_len as usize - } - _ => input.len(), - }; - - let padding_len = input.len() - unpadded_len; - - let err = match *input { - [.., b0] if padding_len == 1 => is_pad_ct(b0) ^ 1, - [.., b0, b1] if padding_len == 2 => (is_pad_ct(b0) & is_pad_ct(b1)) ^ 1, - _ => { - if padding_len == 0 { - 0 - } else { - return Err(InvalidEncodingError); - } - } - }; - - Ok((unpadded_len, err)) -} - -/// Check that the padding of a Base64 encoding string is valid given -/// the decoded buffer. -fn validate_padding(encoded: &[u8], decoded: &[u8]) -> Result<(), Error> { - if !T::PADDED || (encoded.is_empty() && decoded.is_empty()) { - return Ok(()); - } - - let padding_start = encoded.len().checked_sub(4).ok_or(Error::InvalidEncoding)?; - let padding = encoded.get(padding_start..).ok_or(Error::InvalidEncoding)?; - - let decoded_start = if decoded.len() % 3 != 0 { - decoded - .len() - .checked_sub(decoded.len() % 3) - .ok_or(Error::InvalidEncoding)? - } else if decoded.len() == 3 { - 0 - } else { - decoded.len().checked_sub(3).ok_or(Error::InvalidEncoding)? - }; - - let decoded = decoded.get(decoded_start..).ok_or(Error::InvalidEncoding)?; - - let mut buf = [0u8; 4]; - T::encode(decoded, &mut buf)?; - - // Non-short-circuiting comparison of padding - if padding - .iter() - .zip(buf.iter()) - .fold(0, |acc, (a, b)| acc | (a ^ b)) - == 0 - { - Ok(()) - } else { - Err(Error::InvalidEncoding) - } -} - -/// Branchless match that a given byte is the `PAD` character -#[inline(always)] -fn is_pad_ct(input: u8) -> i16 { - ((((PAD as i16 - 1) - input as i16) & (input as i16 - (PAD as i16 + 1))) >> 8) & 1 -} - -#[inline(always)] -const fn encoded_len_inner(n: usize, padded: bool) -> Option { - match n.checked_mul(4) { - Some(q) => { - if padded { - Some(((q / 3) + 3) & !3) - } else { - Some((q / 3) + (q % 3 != 0) as usize) - } - } - None => None, - } -} diff --git a/base64ct/src/errors.rs b/base64ct/src/errors.rs deleted file mode 100644 index 0671a8bb..00000000 --- a/base64ct/src/errors.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! Error types - -use core::fmt; - -const INVALID_ENCODING_MSG: &str = "invalid Base64 encoding"; -const INVALID_LENGTH_MSG: &str = "insufficient output buffer length"; - -/// Insufficient output buffer length. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct InvalidLengthError; - -impl fmt::Display for InvalidLengthError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str(INVALID_LENGTH_MSG) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for InvalidLengthError {} - -/// Invalid encoding of provided Base64 string. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct InvalidEncodingError; - -impl fmt::Display for InvalidEncodingError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str(INVALID_ENCODING_MSG) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for InvalidEncodingError {} - -/// Generic error, union of [`InvalidLengthError`] and [`InvalidEncodingError`]. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Error { - /// Invalid encoding of provided Base64 string. - InvalidEncoding, - - /// Insufficient output buffer length. - InvalidLength, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let s = match self { - Self::InvalidEncoding => INVALID_ENCODING_MSG, - Self::InvalidLength => INVALID_LENGTH_MSG, - }; - f.write_str(s) - } -} - -impl From for Error { - #[inline] - fn from(_: InvalidEncodingError) -> Error { - Error::InvalidEncoding - } -} - -impl From for Error { - #[inline] - fn from(_: InvalidLengthError) -> Error { - Error::InvalidLength - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} diff --git a/base64ct/src/lib.rs b/base64ct/src/lib.rs deleted file mode 100644 index 7f7ea587..00000000 --- a/base64ct/src/lib.rs +++ /dev/null @@ -1,102 +0,0 @@ -//! Pure Rust implementation of Base64 encoding ([RFC 4648, section 4]) -//! with a constant-time `no_std`-friendly implementation. -//! -//! # About -//! -//! This crate implements several Base64 variants in constant-time. -//! -//! The padded variants require (`=`) padding. Unpadded variants expressly -//! reject such padding. -//! -//! Whitespace is expressly disallowed. -//! -//! # Minimum Supported Rust Version -//! -//! This crate requires **Rust 1.47** at a minimum. -//! -//! We may change the MSRV in the future, but it will be accompanied by a minor -//! version bump. -//! -//! # Usage -//! -//! ## Allocating (enable `alloc` crate feature) -//! -//! ``` -//! # #[cfg(feature = "alloc")] -//! # { -//! use base64ct::{Base64, Encoding}; -//! -//! let bytes = b"example bytestring!"; -//! let encoded = Base64::encode_string(bytes); -//! assert_eq!(encoded, "ZXhhbXBsZSBieXRlc3RyaW5nIQ=="); -//! -//! let decoded = Base64::decode_vec(&encoded).unwrap(); -//! assert_eq!(decoded, bytes); -//! # } -//! ``` -//! -//! ## Heapless `no_std` usage -//! -//! ``` -//! use base64ct::{Base64, Encoding}; -//! -//! const BUF_SIZE: usize = 128; -//! -//! let bytes = b"example bytestring!"; -//! assert!(Base64::encoded_len(bytes) <= BUF_SIZE); -//! -//! let mut enc_buf = [0u8; BUF_SIZE]; -//! let encoded = Base64::encode(bytes, &mut enc_buf).unwrap(); -//! assert_eq!(encoded, "ZXhhbXBsZSBieXRlc3RyaW5nIQ=="); -//! -//! let mut dec_buf = [0u8; BUF_SIZE]; -//! let decoded = Base64::decode(encoded, &mut dec_buf).unwrap(); -//! assert_eq!(decoded, bytes); -//! ``` -//! -//! # Implementation -//! -//! Implemented using bitwise arithmetic alone without any lookup tables or -//! data-dependent branches, thereby providing portable "best effort" -//! constant-time operation. -//! -//! Not constant-time with respect to message length (only data). -//! -//! Adapted from the following constant-time C++ implementation of Base64: -//! -//! -//! -//! Copyright (c) 2014 Steve "Sc00bz" Thomas (steve at tobtu dot com). -//! Derived code is dual licensed MIT + Apache 2 (with permission from Sc00bz). -//! -//! [RFC 4648, section 4]: https://tools.ietf.org/html/rfc4648#section-4 - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/base64ct/1.0.1" -)] -#![warn(missing_docs, rust_2018_idioms)] - -#[cfg(feature = "alloc")] -#[macro_use] -extern crate alloc; -#[cfg(feature = "std")] -extern crate std; - -mod encoding; -mod errors; -mod variant; - -pub use crate::{ - encoding::Encoding, - errors::{Error, InvalidEncodingError, InvalidLengthError}, - variant::{ - bcrypt::Base64Bcrypt, - crypt::Base64Crypt, - standard::{Base64, Base64Unpadded}, - url::{Base64Url, Base64UrlUnpadded}, - }, -}; diff --git a/base64ct/src/variant.rs b/base64ct/src/variant.rs deleted file mode 100644 index 1240ce44..00000000 --- a/base64ct/src/variant.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! Base64 variants - -use core::ops::Range; - -pub mod bcrypt; -pub mod crypt; -pub mod standard; -pub mod url; - -/// Core encoder/decoder functions for a particular Base64 variant -pub trait Variant { - /// Is this encoding padded? - const PADDED: bool; - - /// First character in this Base64 alphabet - const BASE: u8; - - /// Decoder passes - const DECODER: &'static [Decode]; - - /// Encoder passes - const ENCODER: &'static [Encode]; - - /// Decode 3 bytes of a Base64 message. - #[inline(always)] - fn decode_3bytes(src: &[u8], dst: &mut [u8]) -> i16 { - debug_assert_eq!(src.len(), 4); - debug_assert!(dst.len() >= 3, "dst too short: {}", dst.len()); - - let c0 = Self::decode_6bits(src[0]); - let c1 = Self::decode_6bits(src[1]); - let c2 = Self::decode_6bits(src[2]); - let c3 = Self::decode_6bits(src[3]); - - dst[0] = ((c0 << 2) | (c1 >> 4)) as u8; - dst[1] = ((c1 << 4) | (c2 >> 2)) as u8; - dst[2] = ((c2 << 6) | c3) as u8; - - ((c0 | c1 | c2 | c3) >> 8) & 1 - } - - /// Decode 6-bits of a Base64 message - fn decode_6bits(src: u8) -> i16 { - let mut res: i16 = -1; - - for decoder in Self::DECODER { - res += match decoder { - Decode::Range(range, offset) => { - // Compute exclusive range from inclusive one - let start = range.start as i16 - 1; - let end = range.end as i16 + 1; - (((start - src as i16) & (src as i16 - end)) >> 8) & (src as i16 + *offset) - } - Decode::Eq(value, offset) => { - let start = *value as i16 - 1; - let end = *value as i16 + 1; - (((start - src as i16) & (src as i16 - end)) >> 8) & *offset - } - }; - } - - res - } - - /// Encode 3-bytes of a Base64 message - #[inline(always)] - fn encode_3bytes(src: &[u8], dst: &mut [u8]) { - debug_assert_eq!(src.len(), 3); - debug_assert!(dst.len() >= 4, "dst too short: {}", dst.len()); - - let b0 = src[0] as i16; - let b1 = src[1] as i16; - let b2 = src[2] as i16; - - dst[0] = Self::encode_6bits(b0 >> 2); - dst[1] = Self::encode_6bits(((b0 << 4) | (b1 >> 4)) & 63); - dst[2] = Self::encode_6bits(((b1 << 2) | (b2 >> 6)) & 63); - dst[3] = Self::encode_6bits(b2 & 63); - } - - /// Encode 6-bits of a Base64 message - #[inline(always)] - fn encode_6bits(src: i16) -> u8 { - let mut diff = src + Self::BASE as i16; - - for &encoder in Self::ENCODER { - diff += match encoder { - Encode::Apply(threshold, offset) => ((threshold as i16 - diff) >> 8) & offset, - Encode::Diff(threshold, offset) => ((threshold as i16 - src) >> 8) & offset, - }; - } - - diff as u8 - } -} - -/// Constant-time decoder step -#[derive(Debug)] -pub enum Decode { - /// Match the given range, offsetting the input on match - Range(Range, i16), - - /// Match the given value, returning the associated offset on match - Eq(u8, i16), -} - -/// Constant-time encoder step -#[derive(Copy, Clone, Debug)] -pub enum Encode { - /// Apply the given offset to the cumulative result on match - Apply(u8, i16), - - /// Compute a difference using the given offset on match - Diff(u8, i16), -} diff --git a/base64ct/src/variant/bcrypt.rs b/base64ct/src/variant/bcrypt.rs deleted file mode 100644 index 01807b63..00000000 --- a/base64ct/src/variant/bcrypt.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! bcrypt Base64 encoding. - -use super::{Decode, Encode, Variant}; - -/// bcrypt Base64 encoding. -/// -/// ```text -/// ./ [A-Z] [a-z] [0-9] -/// 0x2e-0x2f, 0x41-0x5a, 0x61-0x7a, 0x30-0x39 -/// ``` -pub struct Base64Bcrypt; - -impl Variant for Base64Bcrypt { - const PADDED: bool = false; - const BASE: u8 = b'.'; - - const DECODER: &'static [Decode] = &[ - Decode::Range(b'.'..b'/', -45), - Decode::Range(b'A'..b'Z', -62), - Decode::Range(b'a'..b'z', -68), - Decode::Range(b'0'..b'9', 7), - ]; - - const ENCODER: &'static [Encode] = &[ - Encode::Apply(b'/', 17), - Encode::Apply(b'Z', 6), - Encode::Apply(b'z', -75), - ]; -} diff --git a/base64ct/src/variant/crypt.rs b/base64ct/src/variant/crypt.rs deleted file mode 100644 index 70579e02..00000000 --- a/base64ct/src/variant/crypt.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! `crypt(3)` Base64 encoding. - -use super::{Decode, Encode, Variant}; - -/// `crypt(3)` Base64 encoding. -/// -/// ```text -/// [.-9] [A-Z] [a-z] -/// 0x2e-0x39, 0x41-0x5a, 0x61-0x7a -/// ``` -pub struct Base64Crypt; - -impl Variant for Base64Crypt { - const PADDED: bool = false; - const BASE: u8 = b'.'; - - const DECODER: &'static [Decode] = &[ - Decode::Range(b'.'..b'9', -45), - Decode::Range(b'A'..b'Z', -52), - Decode::Range(b'a'..b'z', -58), - ]; - - const ENCODER: &'static [Encode] = &[Encode::Apply(b'9', 7), Encode::Apply(b'Z', 6)]; -} diff --git a/base64ct/src/variant/standard.rs b/base64ct/src/variant/standard.rs deleted file mode 100644 index 98f73206..00000000 --- a/base64ct/src/variant/standard.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Standard Base64 encoding. - -use super::{Decode, Encode, Variant}; - -/// Standard Base64 encoding with `=` padding. -/// -/// ```text -/// [A-Z] [a-z] [0-9] + / -/// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f -/// ``` -pub struct Base64; - -impl Variant for Base64 { - const PADDED: bool = true; - const BASE: u8 = b'A'; - const DECODER: &'static [Decode] = DECODER; - const ENCODER: &'static [Encode] = ENCODER; -} - -/// Standard Base64 encoding *without* padding. -/// -/// ```text -/// [A-Z] [a-z] [0-9] + / -/// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f -/// ``` -pub struct Base64Unpadded; - -impl Variant for Base64Unpadded { - const PADDED: bool = false; - const BASE: u8 = b'A'; - const DECODER: &'static [Decode] = DECODER; - const ENCODER: &'static [Encode] = ENCODER; -} - -/// Standard Base64 decoder -const DECODER: &[Decode] = &[ - Decode::Range(b'A'..b'Z', -64), - Decode::Range(b'a'..b'z', -70), - Decode::Range(b'0'..b'9', 5), - Decode::Eq(b'+', 63), - Decode::Eq(b'/', 64), -]; - -/// Standard Base64 encoder -const ENCODER: &[Encode] = &[ - Encode::Diff(25, 6), - Encode::Diff(51, -75), - Encode::Diff(61, -(b'+' as i16 - 0x1c)), - Encode::Diff(62, b'/' as i16 - b'+' as i16 - 1), -]; diff --git a/base64ct/src/variant/url.rs b/base64ct/src/variant/url.rs deleted file mode 100644 index 415cc519..00000000 --- a/base64ct/src/variant/url.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! URL-safe Base64 encoding. - -use super::{Decode, Encode, Variant}; - -/// URL-safe Base64 encoding with `=` padding. -/// -/// ```text -/// [A-Z] [a-z] [0-9] - _ -/// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2d, 0x5f -/// ``` -pub struct Base64Url; - -impl Variant for Base64Url { - const PADDED: bool = true; - const BASE: u8 = b'A'; - const DECODER: &'static [Decode] = DECODER; - const ENCODER: &'static [Encode] = ENCODER; -} - -/// URL-safe Base64 encoding *without* padding. -/// -/// ```text -/// [A-Z] [a-z] [0-9] - _ -/// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2d, 0x5f -/// ``` -pub struct Base64UrlUnpadded; - -impl Variant for Base64UrlUnpadded { - const PADDED: bool = false; - const BASE: u8 = b'A'; - const DECODER: &'static [Decode] = DECODER; - const ENCODER: &'static [Encode] = ENCODER; -} - -/// URL-safe Base64 decoder -const DECODER: &[Decode] = &[ - Decode::Range(b'A'..b'Z', -64), - Decode::Range(b'a'..b'z', -70), - Decode::Range(b'0'..b'9', 5), - Decode::Eq(b'-', 63), - Decode::Eq(b'_', 64), -]; - -/// URL-safe Base64 encoder -const ENCODER: &[Encode] = &[ - Encode::Diff(25, 6), - Encode::Diff(51, -75), - Encode::Diff(61, -(b'-' as i16 - 0x20)), - Encode::Diff(62, b'_' as i16 - b'-' as i16 - 1), -]; diff --git a/base64ct/tests/bcrypt.rs b/base64ct/tests/bcrypt.rs deleted file mode 100644 index 4ecbfa74..00000000 --- a/base64ct/tests/bcrypt.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! bcrypt Base64 tests - -#[macro_use] -mod common; - -use crate::common::*; -use base64ct::Base64Bcrypt; - -const TEST_VECTORS: &[TestVector] = &[ - TestVector { raw: b"", b64: "" }, - TestVector { - raw: b"\0", - b64: "..", - }, - TestVector { - raw: b"***", - b64: "Igmo", - }, - TestVector { - raw: b"\x01\x02\x03\x04", - b64: ".OGB/.", - }, - TestVector { - raw: b"\xAD\xAD\xAD\xAD\xAD", - b64: "pY0rpYy", - }, - TestVector { - raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", - b64: "98989898", - }, - TestVector { - raw: b"\xFF\xFF\xFF\xFF\xFF", - b64: "9999996", - }, - TestVector { - raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ - \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", - b64: "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i", - }, - TestVector { - raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ - \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ - \xFB\xF0\x00", - b64: "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwx89..", - }, -]; - -impl_tests!(Base64Bcrypt); - -#[test] -fn reject_trailing_whitespace() { - let input = "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i\n"; - let mut buf = [0u8; 1024]; - assert_eq!( - Base64Bcrypt::decode(input, &mut buf), - Err(Error::InvalidEncoding) - ); -} - -#[test] -fn unpadded_reject_trailing_equals() { - let input = "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i="; - let mut buf = [0u8; 1024]; - assert_eq!( - Base64Bcrypt::decode(input, &mut buf), - Err(Error::InvalidEncoding) - ); -} diff --git a/base64ct/tests/common/mod.rs b/base64ct/tests/common/mod.rs deleted file mode 100644 index 3910b33b..00000000 --- a/base64ct/tests/common/mod.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Common testing functionality - -/// Base64 test vector -pub struct TestVector { - pub raw: &'static [u8], - pub b64: &'static str, -} - -/// Generate test suite for a particular Base64 flavor -#[macro_export] -macro_rules! impl_tests { - ($encoding:ty) => { - use base64ct::{Encoding, Error}; - - #[test] - fn encode_test_vectors() { - let mut buf = [0u8; 1024]; - - for vector in TEST_VECTORS { - let out = <$encoding>::encode(vector.raw, &mut buf).unwrap(); - assert_eq!(<$encoding>::encoded_len(vector.raw), vector.b64.len()); - assert_eq!(vector.b64, &out[..]); - - #[cfg(feature = "alloc")] - { - let out = <$encoding>::encode_string(vector.raw); - assert_eq!(vector.b64, &out[..]); - } - } - } - - #[test] - fn decode_test_vectors() { - let mut buf = [0u8; 1024]; - - for vector in TEST_VECTORS { - let out = <$encoding>::decode(vector.b64, &mut buf).unwrap(); - assert_eq!(vector.raw, &out[..]); - - let n = vector.b64.len(); - buf[..n].copy_from_slice(vector.b64.as_bytes()); - let out = <$encoding>::decode_in_place(&mut buf[..n]).unwrap(); - assert_eq!(vector.raw, out); - - #[cfg(feature = "alloc")] - { - let out = <$encoding>::decode_vec(vector.b64).unwrap(); - assert_eq!(vector.raw, &out[..]); - } - } - } - - #[test] - fn encode_and_decode_various_lengths() { - let data = [b'X'; 64]; - let mut inbuf = [0u8; 1024]; - let mut outbuf = [0u8; 1024]; - - for i in 0..data.len() { - let encoded = <$encoding>::encode(&data[..i], &mut inbuf).unwrap(); - - // Make sure it round trips - let decoded = <$encoding>::decode(encoded, &mut outbuf).unwrap(); - assert_eq!(decoded, &data[..i]); - - let elen = <$encoding>::encode(&data[..i], &mut inbuf).unwrap().len(); - let buf = &mut inbuf[..elen]; - let decoded = <$encoding>::decode_in_place(buf).unwrap(); - assert_eq!(decoded, &data[..i]); - - #[cfg(feature = "alloc")] - { - let encoded = <$encoding>::encode_string(&data[..i]); - let decoded = <$encoding>::decode_vec(&encoded).unwrap(); - assert_eq!(decoded, &data[..i]); - } - } - } - }; -} diff --git a/base64ct/tests/crypt.rs b/base64ct/tests/crypt.rs deleted file mode 100644 index cc285f9b..00000000 --- a/base64ct/tests/crypt.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! `crypt(3)` Base64 tests - -#[macro_use] -mod common; - -use crate::common::*; -use base64ct::Base64Crypt; - -const TEST_VECTORS: &[TestVector] = &[ - TestVector { raw: b"", b64: "" }, - TestVector { - raw: b"\0", - b64: "..", - }, - TestVector { - raw: b"***", - b64: "8Wce", - }, - TestVector { - raw: b"\x01\x02\x03\x04", - b64: ".E61/.", - }, - TestVector { - raw: b"\xAD\xAD\xAD\xAD\xAD", - b64: "fOqhfOo", - }, - TestVector { - raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", - b64: "zyzyzyzy", - }, - TestVector { - raw: b"\xFF\xFF\xFF\xFF\xFF", - b64: "zzzzzzw", - }, - TestVector { - raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ - \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", - b64: "EA2zjEJAQWeXkj6FQw/duYZxBGZfn0FZxjbEEEVvpuY", - }, - TestVector { - raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ - \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ - \xFB\xF0\x00", - b64: "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnyz..", - }, -]; - -impl_tests!(Base64Crypt); - -#[test] -fn reject_trailing_whitespace() { - let input = "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i\n"; - let mut buf = [0u8; 1024]; - assert_eq!( - Base64Crypt::decode(input, &mut buf), - Err(Error::InvalidEncoding) - ); -} - -#[test] -fn unpadded_reject_trailing_equals() { - let input = "OKC9tOTKagohutGPa6/n4ij7LQjpxAPj7tlOOOf5z4i="; - let mut buf = [0u8; 1024]; - assert_eq!( - Base64Crypt::decode(input, &mut buf), - Err(Error::InvalidEncoding) - ); -} diff --git a/base64ct/tests/standard.rs b/base64ct/tests/standard.rs deleted file mode 100644 index 4ae32a54..00000000 --- a/base64ct/tests/standard.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Standard Base64 tests - -#[macro_use] -mod common; - -/// Standard Base64 with `=` padding -mod padded { - use crate::common::*; - use base64ct::Base64; - - const TEST_VECTORS: &[TestVector] = &[ - TestVector { raw: b"", b64: "" }, - TestVector { - raw: b"\0", - b64: "AA==", - }, - TestVector { - raw: b"***", - b64: "Kioq", - }, - TestVector { - raw: b"\x01\x02\x03\x04", - b64: "AQIDBA==", - }, - TestVector { - raw: b"\xAD\xAD\xAD\xAD\xAD", - b64: "ra2tra0=", - }, - TestVector { - raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", - b64: "/+/+/+/+", - }, - TestVector { - raw: b"\xFF\xFF\xFF\xFF\xFF", - b64: "//////8=", - }, - TestVector { - raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ - \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", - b64: "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k=", - }, - TestVector { - raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ - \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ - \xFB\xF0\x00", - b64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/AA", - }, - ]; - - impl_tests!(Base64); - - #[test] - fn reject_trailing_whitespace() { - let input = "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k\n"; - let mut buf = [0u8; 1024]; - assert_eq!(Base64::decode(input, &mut buf), Err(Error::InvalidEncoding)); - } - - #[test] - fn reject_invalid_padding() { - let input = "AA/="; - let mut buf = [0u8; 1024]; - assert_eq!(Base64::decode(input, &mut buf), Err(Error::InvalidEncoding)); - } -} - -/// Standard Base64 *without* padding -mod unpadded { - use crate::common::*; - use base64ct::Base64Unpadded; - - const TEST_VECTORS: &[TestVector] = &[ - TestVector { raw: b"", b64: "" }, - TestVector { - raw: b"\0", - b64: "AA", - }, - TestVector { - raw: b"***", - b64: "Kioq", - }, - TestVector { - raw: b"\x01\x02\x03\x04", - b64: "AQIDBA", - }, - TestVector { - raw: b"\xAD\xAD\xAD\xAD\xAD", - b64: "ra2tra0", - }, - TestVector { - raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", - b64: "/+/+/+/+", - }, - TestVector { - raw: b"\xFF\xFF\xFF\xFF\xFF", - b64: "//////8", - }, - TestVector { - raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ - \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", - b64: "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k", - }, - TestVector { - raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ - \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ - \xFB\xF0\x00", - b64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/AA", - }, - ]; - - impl_tests!(Base64Unpadded); - - #[test] - fn reject_trailing_whitespace() { - let input = "EA2zjEJAQWeXkj6FQw/duYZxBGZfn0FZxjbEEEVvpuY\n"; - let mut buf = [0u8; 1024]; - assert_eq!( - Base64Unpadded::decode(input, &mut buf), - Err(Error::InvalidEncoding) - ); - } - - #[test] - fn unpadded_reject_trailing_equals() { - let input = "EA2zjEJAQWeXkj6FQw/duYZxBGZfn0FZxjbEEEVvpuY="; - let mut buf = [0u8; 1024]; - assert_eq!( - Base64Unpadded::decode(input, &mut buf), - Err(Error::InvalidEncoding) - ); - } -} diff --git a/base64ct/tests/url.rs b/base64ct/tests/url.rs deleted file mode 100644 index 1d75ce95..00000000 --- a/base64ct/tests/url.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! URL-safe Base64 tests - -#[macro_use] -mod common; - -/// URL-safe Base64 with `=` padding -mod padded { - use crate::common::*; - use base64ct::Base64Url; - - const TEST_VECTORS: &[TestVector] = &[ - TestVector { raw: b"", b64: "" }, - TestVector { - raw: b"\0", - b64: "AA==", - }, - TestVector { - raw: b"***", - b64: "Kioq", - }, - TestVector { - raw: b"\x01\x02\x03\x04", - b64: "AQIDBA==", - }, - TestVector { - raw: b"\xAD\xAD\xAD\xAD\xAD", - b64: "ra2tra0=", - }, - TestVector { - raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", - b64: "_-_-_-_-", - }, - TestVector { - raw: b"\xFF\xFF\xFF\xFF\xFF", - b64: "______8=", - }, - TestVector { - raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ - \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", - b64: "QME_vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k=", - }, - TestVector { - raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ - \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ - \xFB\xF0\x00", - b64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_AA", - }, - ]; - - impl_tests!(Base64Url); - - #[test] - fn reject_trailing_whitespace() { - let input = "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k\n"; - let mut buf = [0u8; 1024]; - assert_eq!( - Base64Url::decode(input, &mut buf), - Err(Error::InvalidEncoding) - ); - } -} - -/// URL-safe Base64 *without* padding -mod unpadded { - use crate::common::*; - use base64ct::Base64UrlUnpadded; - - const TEST_VECTORS: &[TestVector] = &[ - TestVector { raw: b"", b64: "" }, - TestVector { - raw: b"\0", - b64: "AA", - }, - TestVector { - raw: b"***", - b64: "Kioq", - }, - TestVector { - raw: b"\x01\x02\x03\x04", - b64: "AQIDBA", - }, - TestVector { - raw: b"\xAD\xAD\xAD\xAD\xAD", - b64: "ra2tra0", - }, - TestVector { - raw: b"\xFF\xEF\xFE\xFF\xEF\xFE", - b64: "_-_-_-_-", - }, - TestVector { - raw: b"\xFF\xFF\xFF\xFF\xFF", - b64: "______8", - }, - TestVector { - raw: b"\x40\xC1\x3F\xBD\x05\x4C\x72\x2A\xA3\xC2\xF2\x11\x73\xC0\x69\xEA\ - \x49\x7D\x35\x29\x6B\xCC\x24\x65\xF6\xF9\xD0\x41\x08\x7B\xD7\xA9", - b64: "QME_vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k", - }, - TestVector { - raw: b"\x00\x10\x83\x10Q\x87 \x92\x8B0\xD3\x8FA\x14\x93QU\x97a\x96\x9Bq\ - \xD7\x9F\x82\x18\xA3\x92Y\xA7\xA2\x9A\xAB\xB2\xDB\xAF\xC3\x1C\xB3\ - \xFB\xF0\x00", - b64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_AA", - }, - ]; - - impl_tests!(Base64UrlUnpadded); - - #[test] - fn reject_trailing_whitespace() { - let input = "QME/vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k\n"; - let mut buf = [0u8; 1024]; - assert_eq!( - Base64UrlUnpadded::decode(input, &mut buf), - Err(Error::InvalidEncoding) - ); - } - - #[test] - fn unpadded_reject_trailing_equals() { - let input = "QME_vQVMciqjwvIRc8Bp6kl9NSlrzCRl9vnQQQh716k="; - let mut buf = [0u8; 1024]; - assert_eq!( - Base64UrlUnpadded::decode(input, &mut buf), - Err(Error::InvalidEncoding) - ); - } -} diff --git a/blobby/CHANGELOG.md b/blobby/CHANGELOG.md index fba479b6..254d0a8d 100644 --- a/blobby/CHANGELOG.md +++ b/blobby/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.1 (2021-12-07) +### Added +- `encode_blobs` function ([#280]) + +[#280]: https://github.com/RustCrypto/utils/pull/280 + +## 0.3.0 (2020-07-01) +### Changed +- New storage format with de-duplication capability ([#64]) + +[#64]: https://github.com/RustCrypto/utils/pull/64 + ## 0.2.0 (2020-06-13) ### Added - `Blob5Iterator` diff --git a/blobby/Cargo.toml b/blobby/Cargo.toml index 95026053..b8a12904 100644 --- a/blobby/Cargo.toml +++ b/blobby/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "blobby" -version = "0.3.0" # Also update html_root_url in lib.rs when bumping this +version = "0.3.1" # Also update html_root_url in lib.rs when bumping this authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" description = "Iterator over simple binary blob storage" diff --git a/blobby/src/lib.rs b/blobby/src/lib.rs index 4fa74b21..8affa46b 100644 --- a/blobby/src/lib.rs +++ b/blobby/src/lib.rs @@ -45,9 +45,9 @@ //! [0]: https://en.wikipedia.org/wiki/Variable-length_quantity #![no_std] #![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/blobby/0.3.0" + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_root_url = "https://docs.rs/blobby/0.3.1" )] extern crate alloc; diff --git a/block-buffer/CHANGELOG.md b/block-buffer/CHANGELOG.md index 73d7db09..13c6a936 100644 --- a/block-buffer/CHANGELOG.md +++ b/block-buffer/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.10.2 (2021-02-08) +### Fixed +- Eliminate unreachable panic in `LazyBuffer::digest_blocks` ([#731]) + +[#731]: https://github.com/RustCrypto/utils/pull/731 + +## 0.10.1 (2021-02-05) +### Fixed +- Use `as_mut_ptr` to get a pointer for mutation in the `set_data` method ([#728]) + +[#728]: https://github.com/RustCrypto/utils/pull/728 + +## 0.10.0 (2020-12-07) [YANKED] +### Changed +- Significant reduction of number of unreachable panics. ([#671]) +- Added buffer kind type parameter to `BlockBuffer`, respective marker types, and type aliases. ([#671]) +- Various `BlockBuffer` method changes. ([#671]) + +### Removed +- `pad_with` method and dependency on `block-padding`. ([#671]) + +[#671]: https://github.com/RustCrypto/utils/pull/671 + ## 0.10.0 (2020-12-08) ### Changed - Rename `input_block(s)` methods to `digest_block(s)`. ([#113]) diff --git a/block-buffer/Cargo.toml b/block-buffer/Cargo.toml index 124e5a7a..680674a1 100644 --- a/block-buffer/Cargo.toml +++ b/block-buffer/Cargo.toml @@ -1,15 +1,15 @@ [package] name = "block-buffer" -version = "0.10.0-pre.4" # Also update html_root_url in lib.rs when bumping this +version = "0.10.2" # Also update html_root_url in lib.rs when bumping this authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" -description = "Fixed size buffer for block processing of data" +description = "Buffer type for block processing of data" documentation = "https://docs.rs/block-buffer" repository = "https://github.com/RustCrypto/utils" keywords = ["block", "buffer"] categories = ["cryptography", "no-std"] edition = "2018" +readme = "README.md" [dependencies] -block-padding = { version = "0.3.0-pre", path = "../block-padding", optional = true } generic-array = "0.14" diff --git a/block-buffer/README.md b/block-buffer/README.md new file mode 100644 index 00000000..cb134cc9 --- /dev/null +++ b/block-buffer/README.md @@ -0,0 +1,40 @@ +# [RustCrypto]: Block Buffer + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Buffer type for block processing of data with minimized amount of unreachable panics. + +## License + +Licensed under either of: + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/block-buffer.svg +[crate-link]: https://crates.io/crates/block-buffer +[docs-image]: https://docs.rs/block-buffer/badge.svg +[docs-link]: https://docs.rs/block-buffer/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.41+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils +[build-image]: https://github.com/RustCrypto/utils/workflows/block-buffer/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/utils/actions/workflows/block-buffer.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/rustcrypto diff --git a/block-buffer/src/buffer.rs b/block-buffer/src/buffer.rs deleted file mode 100644 index fd0a0806..00000000 --- a/block-buffer/src/buffer.rs +++ /dev/null @@ -1,308 +0,0 @@ -#[cfg(feature = "block-padding")] -use block_padding::Padding; - -use crate::{ - utils::{to_blocks, to_blocks_mut}, - Block, DigestBuffer, InvalidLength, ParBlock, -}; -use core::slice; -use generic_array::{typenum::U1, ArrayLength}; - -/// Buffer for block processing of data. -#[derive(Clone, Default)] -pub struct BlockBuffer> { - buffer: Block, - pos: usize, -} - -impl> BlockBuffer { - /// Core method for `xor_data` and `set_data` methods. - /// - /// If `N` is equal to 1, the `gen_blocks` function is not used. - fn process_data>>( - &mut self, - mut data: &mut [u8], - state: &mut S, - mut process: impl FnMut(&mut [u8], &[u8]), - mut gen_block: impl FnMut(&mut S) -> Block, - mut gen_blocks: impl FnMut(&mut S) -> ParBlock, - ) { - let pos = self.get_pos(); - let r = self.remaining(); - let n = data.len(); - if pos != 0 { - if n < r { - // double slicing allows to remove panic branches - process(data, &self.buffer[pos..][..n]); - self.set_pos_unchecked(pos + n); - return; - } - let (left, right) = data.split_at_mut(r); - data = right; - process(left, &self.buffer[pos..]); - } - - let (par_blocks, blocks, leftover) = to_blocks_mut::(data); - for pb in par_blocks { - let blocks = gen_blocks(state); - for i in 0..N::USIZE { - process(&mut pb[i], &blocks[i]); - } - } - - for block in blocks { - process(block, &gen_block(state)); - } - - let n = leftover.len(); - if n != 0 { - let block = gen_block(state); - process(leftover, &block[..n]); - self.buffer = block; - } - self.set_pos_unchecked(n); - } - - /// XORs `data` using the provided state and block generation functions. - /// - /// This method is intended for stream cipher implementations. If `N` is - /// equal to 1, the `gen_blocks` function is not used. - #[inline] - pub fn par_xor_data>>( - &mut self, - data: &mut [u8], - state: &mut S, - gen_block: impl FnMut(&mut S) -> Block, - gen_blocks: impl FnMut(&mut S) -> ParBlock, - ) { - self.process_data(data, state, xor, gen_block, gen_blocks); - } - - /// Simplified version of the [`par_xor_data`][BlockBuffer::par_xor_data] method, with `N = 1`. - #[inline] - pub fn xor_data(&mut self, data: &mut [u8], mut gen_block: impl FnMut() -> Block) { - // note: the unrachable panic should be removed by compiler since - // with `N = 1` the second closure is not used - self.process_data(data, &mut gen_block, xor, |f| f(), unreachable); - } - - /// Set `data` to generated blocks. - #[inline] - pub fn set_data(&mut self, data: &mut [u8], mut gen_block: impl FnMut() -> Block) { - // note: the unrachable panic should be removed by compiler since - // with `N = 1` the second closure is not used - self.process_data(data, &mut gen_block, set, |f| f(), unreachable); - } - - /// Process `data` in blocks and write result to `out_buf`, storing - /// leftovers for future use. - #[inline] - pub fn block_mode_processing<'a>( - &mut self, - mut data: &[u8], - buf: &'a mut [u8], - mut process: impl FnMut(&mut [Block]), - ) -> Result<&'a [u8], InvalidLength> { - let pos = self.get_pos(); - let rem = self.remaining(); - let mut blocks_processed = 0; - let (_, mut buf_blocks, _) = to_blocks_mut::(buf); - if pos != 0 { - let n = data.len(); - if n < rem { - // double slicing allows to remove panic branches - self.buffer[pos..][..n].copy_from_slice(data); - self.set_pos_unchecked(pos + n); - return Ok(&buf[..0]); - } - if buf_blocks.is_empty() { - return Err(InvalidLength); - } - - let (l, r) = buf_blocks.split_at_mut(1); - let buf_block = &mut l[0]; - buf_blocks = r; - let (l, r) = data.split_at(rem); - data = r; - - buf_block[..pos].copy_from_slice(&self.buffer[..pos]); - buf_block[pos..].copy_from_slice(l); - - process(slice::from_mut(buf_block)); - blocks_processed += 1; - } - - let (data_blocks, leftover) = to_blocks(data); - let buf_blocks = buf_blocks - .get_mut(..data_blocks.len()) - .ok_or(InvalidLength)?; - buf_blocks.clone_from_slice(data_blocks); - process(buf_blocks); - blocks_processed += buf_blocks.len(); - - let n = leftover.len(); - self.buffer[..n].copy_from_slice(leftover); - self.set_pos_unchecked(n); - - let res = unsafe { - let res_len = BlockSize::USIZE * blocks_processed; - // SAFETY: number of processed blocks never exceeds capacity of `buf` - debug_assert!(buf.len() >= res_len); - buf.get_unchecked(..res_len) - }; - Ok(res) - } - - /// Compress remaining data after padding it with `delim`, zeros and - /// the `suffix` bytes. If there is not enough unused space, `compress` - /// will be called twice. - #[inline(always)] - pub fn digest_pad( - &mut self, - delim: u8, - suffix: &[u8], - mut compress: impl FnMut(&Block), - ) { - let pos = self.get_pos(); - self.buffer[pos] = delim; - for b in &mut self.buffer[pos + 1..] { - *b = 0; - } - - let n = self.size() - suffix.len(); - if self.size() - pos - 1 < suffix.len() { - compress(&self.buffer); - let mut block: Block = Default::default(); - block[n..].copy_from_slice(suffix); - compress(&block); - } else { - self.buffer[n..].copy_from_slice(suffix); - compress(&self.buffer); - } - self.set_pos_unchecked(0) - } - - /// Pad message with 0x80, zeros and 64-bit message length using - /// big-endian byte order. - #[inline] - pub fn len64_padding_be(&mut self, data_len: u64, compress: impl FnMut(&Block)) { - self.digest_pad(0x80, &data_len.to_be_bytes(), compress); - } - - /// Pad message with 0x80, zeros and 64-bit message length using - /// little-endian byte order. - #[inline] - pub fn len64_padding_le(&mut self, data_len: u64, compress: impl FnMut(&Block)) { - self.digest_pad(0x80, &data_len.to_le_bytes(), compress); - } - - /// Pad message with 0x80, zeros and 128-bit message length using - /// big-endian byte order. - #[inline] - pub fn len128_padding_be(&mut self, data_len: u128, compress: impl FnMut(&Block)) { - self.digest_pad(0x80, &data_len.to_be_bytes(), compress); - } - - /// Pad message with a given padding `P`. - #[cfg(feature = "block-padding")] - #[inline] - pub fn pad_with>(&mut self) -> &mut Block { - let pos = self.get_pos(); - P::pad(&mut self.buffer, pos); - self.set_pos_unchecked(0); - &mut self.buffer - } - - /// Return size of the internall buffer in bytes. - #[inline] - pub fn size(&self) -> usize { - BlockSize::USIZE - } - - /// Return number of remaining bytes in the internall buffer. - #[inline] - pub fn remaining(&self) -> usize { - self.size() - self.get_pos() - } - - /// Reset buffer by setting cursor position to zero. - #[inline] - pub fn reset(&mut self) { - self.pos = 0 - } - - /// Return current cursor position. - #[inline] - pub fn get_pos(&self) -> usize { - debug_assert!(self.pos < BlockSize::USIZE); - if self.pos >= BlockSize::USIZE { - // SAFETY: `pos` is set only to values smaller than block size - unsafe { core::hint::unreachable_unchecked() } - } - self.pos - } - - /// Set buffer content and cursor position. - /// - /// # Panics - /// If `pos` is bigger or equal to block size. - pub fn set(&mut self, buf: Block, pos: usize) { - assert!(pos < BlockSize::USIZE); - self.buffer = buf; - self.pos = pos; - } - - #[inline] - fn set_pos_unchecked(&mut self, pos: usize) { - debug_assert!(pos < BlockSize::USIZE); - self.pos = pos; - } -} - -impl> DigestBuffer for BlockBuffer { - #[inline] - fn digest_blocks(&mut self, mut input: &[u8], mut compress: impl FnMut(&[Block])) { - let pos = self.get_pos(); - let r = self.remaining(); - let n = input.len(); - if n < r { - // double slicing allows to remove panic branches - self.buffer[pos..][..n].copy_from_slice(input); - self.set_pos_unchecked(pos + n); - return; - } - if pos != 0 { - let (left, right) = input.split_at(r); - input = right; - self.buffer[pos..].copy_from_slice(left); - compress(slice::from_ref(&self.buffer)); - } - - let (blocks, leftover) = to_blocks(input); - compress(blocks); - - let n = leftover.len(); - self.buffer[..n].copy_from_slice(leftover); - self.set_pos_unchecked(n); - } - - #[inline] - fn reset(&mut self) { - self.pos = 0; - } -} - -#[inline(always)] -fn xor(a: &mut [u8], b: &[u8]) { - debug_assert_eq!(a.len(), b.len()); - a.iter_mut().zip(b.iter()).for_each(|(a, &b)| *a ^= b); -} - -#[inline(always)] -fn set(a: &mut [u8], b: &[u8]) { - a.copy_from_slice(b); -} - -fn unreachable>(_: &mut S) -> ParBlock { - unreachable!(); -} diff --git a/block-buffer/src/lazy.rs b/block-buffer/src/lazy.rs deleted file mode 100644 index 2a705ebe..00000000 --- a/block-buffer/src/lazy.rs +++ /dev/null @@ -1,184 +0,0 @@ -use crate::{ - utils::{to_blocks, to_blocks_mut}, - Block, DigestBuffer, InvalidLength, -}; -use core::slice; -use generic_array::{typenum::U1, ArrayLength}; - -/// Buffer for lazy block processing of data. -#[derive(Clone, Default)] -pub struct LazyBlockBuffer> { - buffer: Block, - pos: usize, -} - -impl> LazyBlockBuffer { - /// Process `data` in blocks and write result to `out_buf`, storing - /// leftovers for future use. - #[inline] - pub fn block_mode_processing<'a>( - &mut self, - mut data: &[u8], - buf: &'a mut [u8], - mut process: impl FnMut(&mut [Block]), - ) -> Result<&'a [u8], InvalidLength> { - let pos = self.get_pos(); - let rem = self.remaining(); - let mut blocks_processed = 0; - let (_, mut buf_blocks, _) = to_blocks_mut::(buf); - if pos != 0 { - let n = data.len(); - if n <= rem { - // double slicing allows to remove panic branches - self.buffer[pos..][..n].copy_from_slice(data); - self.set_pos_unchecked(pos + n); - return Ok(&buf[..0]); - } - if buf_blocks.is_empty() { - return Err(InvalidLength); - } - - let (l, r) = buf_blocks.split_at_mut(1); - let buf_block = &mut l[0]; - buf_blocks = r; - let (l, r) = data.split_at(rem); - data = r; - - buf_block[..pos].copy_from_slice(&self.buffer[..pos]); - buf_block[pos..].copy_from_slice(l); - - process(slice::from_mut(buf_block)); - blocks_processed += 1; - } - - let (data_blocks, leftover) = to_blocks_lazy(data); - let buf_blocks = buf_blocks - .get_mut(..data_blocks.len()) - .ok_or(InvalidLength)?; - buf_blocks.clone_from_slice(data_blocks); - process(buf_blocks); - blocks_processed += buf_blocks.len(); - - let n = leftover.len(); - self.buffer[..n].copy_from_slice(leftover); - self.set_pos_unchecked(n); - - let res = unsafe { - let res_len = BlockSize::USIZE * blocks_processed; - // SAFETY: number of processed blocks never exceeds capacity of `buf` - debug_assert!(buf.len() >= res_len); - buf.get_unchecked(..res_len) - }; - Ok(res) - } - - /// Pad remaining data with zeros and call `compress` with resulting block. - pub fn pad_zeros(&mut self) -> &mut Block { - let pos = self.get_pos(); - self.buffer[pos..].iter_mut().for_each(|b| *b = 0); - self.set_pos_unchecked(0); - &mut self.buffer - } - - /// Return block if buffer is full, otherwise returns `None`. - #[inline] - pub fn get_full_block(&mut self) -> Option<&mut Block> { - match self.remaining() { - 0 => Some(&mut self.buffer), - _ => None, - } - } - - /// Return size of the internall buffer in bytes. - #[inline] - pub fn size(&self) -> usize { - BlockSize::USIZE - } - - /// Return number of remaining bytes in the internall buffer. - #[inline] - pub fn remaining(&self) -> usize { - self.size() - self.get_pos() - } - - /// Reset buffer by setting cursor position to zero. - #[inline] - pub fn reset(&mut self) { - self.pos = 0 - } - - /// Return current cursor position. - #[inline] - pub fn get_pos(&self) -> usize { - debug_assert!(self.pos <= BlockSize::USIZE); - if self.pos > BlockSize::USIZE { - // SAFETY: `pos` is set only to values smaller or equal to block size - unsafe { core::hint::unreachable_unchecked() } - } - self.pos - } - - /// Set buffer content and cursor position. - /// - /// # Panics - /// If `pos` is bigger or equal to block size. - pub fn set(&mut self, buf: Block, pos: usize) { - assert!(pos <= BlockSize::USIZE); - self.buffer = buf; - self.pos = pos; - } - - #[inline] - fn set_pos_unchecked(&mut self, pos: usize) { - debug_assert!(pos <= BlockSize::USIZE); - self.pos = pos; - } -} - -impl> DigestBuffer for LazyBlockBuffer { - #[inline] - fn digest_blocks(&mut self, mut input: &[u8], mut compress: impl FnMut(&[Block])) { - let pos = self.get_pos(); - let r = self.remaining(); - let n = input.len(); - if n <= r { - // double slicing allows to remove panic branches - self.buffer[pos..][..n].copy_from_slice(input); - self.set_pos_unchecked(pos + n); - return; - } - if pos != 0 { - let (left, right) = input.split_at(r); - input = right; - self.buffer[pos..].copy_from_slice(left); - compress(core::slice::from_ref(&self.buffer)); - } - - let (blocks, leftover) = to_blocks_lazy(input); - compress(blocks); - - let n = leftover.len(); - self.buffer[..n].copy_from_slice(leftover); - self.set_pos_unchecked(n); - } - - #[inline] - fn reset(&mut self) { - self.pos = 0; - } -} - -fn to_blocks_lazy>(data: &[u8]) -> (&[Block], &[u8]) { - let (mut blocks, mut leftover) = to_blocks(data); - if leftover.is_empty() { - debug_assert!(!blocks.is_empty()); - let m = blocks.len() - 1; - // SAFETY: at this stage `input` always contains at least one byte, - // so either `leftover` is not empty or we have at least one block - unsafe { - leftover = blocks.get_unchecked(m); - blocks = blocks.get_unchecked(..m); - } - } - (blocks, leftover) -} diff --git a/block-buffer/src/lib.rs b/block-buffer/src/lib.rs index 76274efb..ebe73695 100644 --- a/block-buffer/src/lib.rs +++ b/block-buffer/src/lib.rs @@ -1,48 +1,325 @@ //! Fixed size buffer for block processing of data. #![no_std] #![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/block-buffer/0.10.0-pre.4" + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_root_url = "https://docs.rs/block-buffer/0.10.2" )] #![warn(missing_docs, rust_2018_idioms)] -#[cfg(feature = "block-padding")] -pub use block_padding; pub use generic_array; -use core::fmt; -use generic_array::{ArrayLength, GenericArray}; +use core::{marker::PhantomData, slice}; +use generic_array::{ + typenum::{IsLess, Le, NonZero, U256}, + ArrayLength, GenericArray, +}; -mod buffer; -mod lazy; -mod utils; +mod sealed; -pub use buffer::BlockBuffer; -pub use lazy::LazyBlockBuffer; - -/// Block on which a `BlockBuffer` operates. +/// Block on which `BlockBuffer` operates. pub type Block = GenericArray; -/// Blocks being acted over in parallel. -pub type ParBlock = GenericArray, ParBlocks>; -/// Trait which generalizes digest functionality of buffers. -pub trait DigestBuffer>: Default { +/// Trait for buffer kinds. +pub trait BufferKind: sealed::Sealed {} + +/// Eager block buffer kind, which guarantees that buffer position +/// always lies in the range of `0..BlockSize`. +#[derive(Copy, Clone, Debug, Default)] +pub struct Eager {} + +/// Lazy block buffer kind, which guarantees that buffer position +/// always lies in the range of `0..=BlockSize`. +#[derive(Copy, Clone, Debug, Default)] +pub struct Lazy {} + +impl BufferKind for Eager {} +impl BufferKind for Lazy {} + +/// Eager block buffer. +pub type EagerBuffer = BlockBuffer; +/// Lazy block buffer. +pub type LazyBuffer = BlockBuffer; + +/// Buffer for block processing of data. +#[derive(Debug)] +pub struct BlockBuffer +where + BlockSize: ArrayLength + IsLess, + Le: NonZero, + Kind: BufferKind, +{ + buffer: Block, + pos: u8, + _pd: PhantomData, +} + +impl Default for BlockBuffer +where + BlockSize: ArrayLength + IsLess, + Le: NonZero, + Kind: BufferKind, +{ + fn default() -> Self { + Self { + buffer: Default::default(), + pos: 0, + _pd: PhantomData, + } + } +} + +impl Clone for BlockBuffer +where + BlockSize: ArrayLength + IsLess, + Le: NonZero, + Kind: BufferKind, +{ + fn clone(&self) -> Self { + Self { + buffer: self.buffer.clone(), + pos: self.pos, + _pd: PhantomData, + } + } +} + +impl BlockBuffer +where + BlockSize: ArrayLength + IsLess, + Le: NonZero, + Kind: BufferKind, +{ + /// Create new buffer from slice. + /// + /// # Panics + /// If slice length is not valid for used buffer kind. + #[inline(always)] + pub fn new(buf: &[u8]) -> Self { + let pos = buf.len(); + assert!(Kind::invariant(pos, BlockSize::USIZE)); + let mut buffer = Block::::default(); + buffer[..pos].copy_from_slice(buf); + Self { + buffer, + pos: pos as u8, + _pd: PhantomData, + } + } + /// Digest data in `input` in blocks of size `BlockSize` using /// the `compress` function, which accepts slice of blocks. - fn digest_blocks(&mut self, input: &[u8], compress: impl FnMut(&[Block])); + #[inline] + pub fn digest_blocks( + &mut self, + mut input: &[u8], + mut compress: impl FnMut(&[Block]), + ) { + let pos = self.get_pos(); + // using `self.remaining()` for some reason + // prevents panic elimination + let rem = self.size() - pos; + let n = input.len(); + // Note that checking condition `pos + n < BlockSize` is + // equivalent to checking `n < rem`, where `rem` is equal + // to `BlockSize - pos`. Using the latter allows us to work + // around compiler accounting for possible overflow of + // `pos + n` which results in it inserting unreachable + // panic branches. Using `unreachable_unchecked` in `get_pos` + // we convince compiler that `BlockSize - pos` never underflows. + if Kind::invariant(n, rem) { + // double slicing allows to remove panic branches + self.buffer[pos..][..n].copy_from_slice(input); + self.set_pos_unchecked(pos + n); + return; + } + if pos != 0 { + let (left, right) = input.split_at(rem); + input = right; + self.buffer[pos..].copy_from_slice(left); + compress(slice::from_ref(&self.buffer)); + } + + let (blocks, leftover) = Kind::split_blocks(input); + if !blocks.is_empty() { + compress(blocks); + } + + let n = leftover.len(); + self.buffer[..n].copy_from_slice(leftover); + self.set_pos_unchecked(n); + } /// Reset buffer by setting cursor position to zero. - fn reset(&mut self); + #[inline(always)] + pub fn reset(&mut self) { + self.set_pos_unchecked(0); + } + + /// Pad remaining data with zeros and return resulting block. + #[inline(always)] + pub fn pad_with_zeros(&mut self) -> &mut Block { + let pos = self.get_pos(); + self.buffer[pos..].iter_mut().for_each(|b| *b = 0); + self.set_pos_unchecked(0); + &mut self.buffer + } + + /// Return current cursor position. + #[inline(always)] + pub fn get_pos(&self) -> usize { + let pos = self.pos as usize; + if !Kind::invariant(pos, BlockSize::USIZE) { + debug_assert!(false); + // SAFETY: `pos` never breaks the invariant + unsafe { + core::hint::unreachable_unchecked(); + } + } + pos + } + + /// Return slice of data stored inside the buffer. + #[inline(always)] + pub fn get_data(&self) -> &[u8] { + &self.buffer[..self.get_pos()] + } + + /// Set buffer content and cursor position. + /// + /// # Panics + /// If `pos` is bigger or equal to block size. + #[inline] + pub fn set(&mut self, buf: Block, pos: usize) { + assert!(Kind::invariant(pos, BlockSize::USIZE)); + self.buffer = buf; + self.set_pos_unchecked(pos); + } + + /// Return size of the internall buffer in bytes. + #[inline(always)] + pub fn size(&self) -> usize { + BlockSize::USIZE + } + + /// Return number of remaining bytes in the internall buffer. + #[inline(always)] + pub fn remaining(&self) -> usize { + self.size() - self.get_pos() + } + + #[inline(always)] + fn set_pos_unchecked(&mut self, pos: usize) { + debug_assert!(Kind::invariant(pos, BlockSize::USIZE)); + self.pos = pos as u8; + } } -/// Error type used by the [`BlockBuffer::block_mode_processing`] and -/// [`LazyBlockBuffer::block_mode_processing`] methods. -#[derive(Copy, Clone, Debug)] -pub struct InvalidLength; +impl BlockBuffer +where + BlockSize: ArrayLength + IsLess, + Le: NonZero, +{ + /// Set `data` to generated blocks. + #[inline] + pub fn set_data( + &mut self, + mut data: &mut [u8], + mut process_blocks: impl FnMut(&mut [Block]), + ) { + let pos = self.get_pos(); + let r = self.remaining(); + let n = data.len(); + if pos != 0 { + if n < r { + // double slicing allows to remove panic branches + data.copy_from_slice(&self.buffer[pos..][..n]); + self.set_pos_unchecked(pos + n); + return; + } + let (left, right) = data.split_at_mut(r); + data = right; + left.copy_from_slice(&self.buffer[pos..]); + } + + let (blocks, leftover) = to_blocks_mut(data); + process_blocks(blocks); -impl fmt::Display for InvalidLength { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str("Invalid Length") + let n = leftover.len(); + if n != 0 { + let mut block = Default::default(); + process_blocks(slice::from_mut(&mut block)); + leftover.copy_from_slice(&block[..n]); + self.buffer = block; + } + self.set_pos_unchecked(n); } + + /// Compress remaining data after padding it with `delim`, zeros and + /// the `suffix` bytes. If there is not enough unused space, `compress` + /// will be called twice. + /// + /// # Panics + /// If suffix length is bigger than block size. + #[inline(always)] + pub fn digest_pad( + &mut self, + delim: u8, + suffix: &[u8], + mut compress: impl FnMut(&Block), + ) { + if suffix.len() > BlockSize::USIZE { + panic!("suffix is too long"); + } + let pos = self.get_pos(); + self.buffer[pos] = delim; + for b in &mut self.buffer[pos + 1..] { + *b = 0; + } + + let n = self.size() - suffix.len(); + if self.size() - pos - 1 < suffix.len() { + compress(&self.buffer); + let mut block = Block::::default(); + block[n..].copy_from_slice(suffix); + compress(&block); + } else { + self.buffer[n..].copy_from_slice(suffix); + compress(&self.buffer); + } + self.set_pos_unchecked(0) + } + + /// Pad message with 0x80, zeros and 64-bit message length using + /// big-endian byte order. + #[inline] + pub fn len64_padding_be(&mut self, data_len: u64, compress: impl FnMut(&Block)) { + self.digest_pad(0x80, &data_len.to_be_bytes(), compress); + } + + /// Pad message with 0x80, zeros and 64-bit message length using + /// little-endian byte order. + #[inline] + pub fn len64_padding_le(&mut self, data_len: u64, compress: impl FnMut(&Block)) { + self.digest_pad(0x80, &data_len.to_le_bytes(), compress); + } + + /// Pad message with 0x80, zeros and 128-bit message length using + /// big-endian byte order. + #[inline] + pub fn len128_padding_be(&mut self, data_len: u128, compress: impl FnMut(&Block)) { + self.digest_pad(0x80, &data_len.to_be_bytes(), compress); + } +} + +/// Split message into mutable slice of parallel blocks, blocks, and leftover bytes. +#[inline(always)] +fn to_blocks_mut>(data: &mut [u8]) -> (&mut [Block], &mut [u8]) { + let nb = data.len() / N::USIZE; + let (left, right) = data.split_at_mut(nb * N::USIZE); + let p = left.as_mut_ptr() as *mut Block; + // SAFETY: we guarantee that `blocks` does not point outside of `data`, and `p` is valid for + // mutation + let blocks = unsafe { slice::from_raw_parts_mut(p, nb) }; + (blocks, right) } diff --git a/block-buffer/src/sealed.rs b/block-buffer/src/sealed.rs new file mode 100644 index 00000000..371a8b70 --- /dev/null +++ b/block-buffer/src/sealed.rs @@ -0,0 +1,67 @@ +use super::{ArrayLength, Block}; +use core::slice; + +/// Sealed trait for buffer kinds. +pub trait Sealed { + /// Invariant guaranteed by a buffer kind, i.e. with correct + /// buffer code this function always returns true. + fn invariant(pos: usize, block_size: usize) -> bool; + + /// Split input data into slice fo blocks and tail. + fn split_blocks>(data: &[u8]) -> (&[Block], &[u8]); +} + +impl Sealed for super::Eager { + #[inline(always)] + fn invariant(pos: usize, block_size: usize) -> bool { + pos < block_size + } + + #[inline(always)] + fn split_blocks>(data: &[u8]) -> (&[Block], &[u8]) { + let nb = data.len() / N::USIZE; + let blocks_len = nb * N::USIZE; + let tail_len = data.len() - blocks_len; + // SAFETY: we guarantee that created slices do not point + // outside of `data` + unsafe { + let blocks_ptr = data.as_ptr() as *const Block; + let tail_ptr = data.as_ptr().add(blocks_len); + ( + slice::from_raw_parts(blocks_ptr, nb), + slice::from_raw_parts(tail_ptr, tail_len), + ) + } + } +} + +impl Sealed for super::Lazy { + #[inline(always)] + fn invariant(pos: usize, block_size: usize) -> bool { + pos <= block_size + } + + #[inline(always)] + fn split_blocks>(data: &[u8]) -> (&[Block], &[u8]) { + if data.is_empty() { + return (&[], &[]); + } + let (nb, tail_len) = if data.len() % N::USIZE == 0 { + (data.len() / N::USIZE - 1, N::USIZE) + } else { + let nb = data.len() / N::USIZE; + (nb, data.len() - nb * N::USIZE) + }; + let blocks_len = nb * N::USIZE; + // SAFETY: we guarantee that created slices do not point + // outside of `data` + unsafe { + let blocks_ptr = data.as_ptr() as *const Block; + let tail_ptr = data.as_ptr().add(blocks_len); + ( + slice::from_raw_parts(blocks_ptr, nb), + slice::from_raw_parts(tail_ptr, tail_len), + ) + } + } +} diff --git a/block-buffer/src/utils.rs b/block-buffer/src/utils.rs deleted file mode 100644 index 707e2c5a..00000000 --- a/block-buffer/src/utils.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{Block, ParBlock}; -use core::slice; -use generic_array::ArrayLength; - -/// Split message into slice of blocks and leftover bytes. -#[inline(always)] -pub(crate) fn to_blocks>(data: &[u8]) -> (&[Block], &[u8]) { - let nb = data.len() / N::USIZE; - let (left, right) = data.split_at(nb * N::USIZE); - let p = left.as_ptr() as *const Block; - // SAFETY: we guarantee that `blocks` does not point outside of `data` - let blocks = unsafe { slice::from_raw_parts(p, nb) }; - (blocks, right) -} - -/// Split message into mutable slice of parallel blocks, blocks, and leftover bytes. -#[allow(clippy::type_complexity)] -#[inline(always)] -pub(crate) fn to_blocks_mut, M: ArrayLength>>( - data: &mut [u8], -) -> (&mut [ParBlock], &mut [Block], &mut [u8]) { - let b_size = N::USIZE; - let pb_size = N::USIZE * M::USIZE; - let npb = match M::USIZE { - 1 => 0, - _ => data.len() / pb_size, - }; - let (pb_slice, data) = data.split_at_mut(npb * pb_size); - let nb = data.len() / b_size; - let (b_slice, data) = data.split_at_mut(nb * b_size); - let pb_ptr = pb_slice.as_mut_ptr() as *mut ParBlock; - let b_ptr = b_slice.as_mut_ptr() as *mut Block; - // SAFETY: we guarantee that the resulting values do not overlap and do not - // point outside of the input slice - unsafe { - ( - slice::from_raw_parts_mut(pb_ptr, npb), - slice::from_raw_parts_mut(b_ptr, nb), - data, - ) - } -} diff --git a/block-buffer/tests/mod.rs b/block-buffer/tests/mod.rs new file mode 100644 index 00000000..6dfa1ae1 --- /dev/null +++ b/block-buffer/tests/mod.rs @@ -0,0 +1,188 @@ +use block_buffer::{ + generic_array::typenum::{U10, U16, U24, U4, U8}, + Block, EagerBuffer, LazyBuffer, +}; + +#[test] +fn test_eager_digest_pad() { + let mut buf = EagerBuffer::::default(); + let inputs = [ + &b"01234567"[..], + &b"89"[..], + &b"abcdefghij"[..], + &b"klmnopqrs"[..], + &b"tuv"[..], + &b"wx"[..], + ]; + let exp_blocks = [ + (0, &[b"0123", b"4567"][..]), + (2, &[b"89ab"][..]), + (2, &[b"cdef", b"ghij"][..]), + (3, &[b"klmn", b"opqr"][..]), + (4, &[b"stuv"][..]), + ]; + let exp_poses = [0, 2, 0, 1, 0, 2]; + + let mut n = 0; + for (i, input) in inputs.iter().enumerate() { + buf.digest_blocks(input, |b| { + let (j, exp) = exp_blocks[n]; + n += 1; + assert_eq!(i, j); + assert_eq!(b.len(), exp.len()); + assert!(b.iter().zip(exp.iter()).all(|v| v.0[..] == v.1[..])); + }); + assert_eq!(exp_poses[i], buf.get_pos()); + } + assert_eq!(buf.pad_with_zeros()[..], b"wx\0\0"[..]); + assert_eq!(buf.get_pos(), 0); +} + +#[test] +fn test_lazy_digest_pad() { + let mut buf = LazyBuffer::::default(); + let inputs = [ + &b"01234567"[..], + &b"89"[..], + &b"abcdefghij"[..], + &b"klmnopqrs"[..], + ]; + let expected = [ + (0, &[b"0123"][..]), + (1, &[b"4567"][..]), + (2, &[b"89ab"][..]), + (2, &[b"cdef"][..]), + (3, &[b"ghij"][..]), + (3, &[b"klmn", b"opqr"][..]), + ]; + let exp_poses = [4, 2, 4, 1]; + + let mut n = 0; + for (i, input) in inputs.iter().enumerate() { + buf.digest_blocks(input, |b| { + let (j, exp) = expected[n]; + n += 1; + assert_eq!(i, j); + assert_eq!(b.len(), exp.len()); + assert!(b.iter().zip(exp.iter()).all(|v| v.0[..] == v.1[..])); + }); + assert_eq!(exp_poses[i], buf.get_pos()); + } + assert_eq!(buf.pad_with_zeros()[..], b"s\0\0\0"[..]); + assert_eq!(buf.get_pos(), 0); +} + +#[test] +fn test_eager_set_data() { + let mut buf = EagerBuffer::::default(); + + let mut n = 0u8; + let mut gen = |blocks: &mut [Block]| { + for block in blocks { + block.iter_mut().for_each(|b| *b = n); + n += 1; + } + }; + + let mut out = [0u8; 6]; + buf.set_data(&mut out, &mut gen); + assert_eq!(out, [0, 0, 0, 0, 1, 1]); + assert_eq!(buf.get_pos(), 2); + + let mut out = [0u8; 3]; + buf.set_data(&mut out, &mut gen); + assert_eq!(out, [1, 1, 2]); + assert_eq!(buf.get_pos(), 1); + + let mut out = [0u8; 3]; + buf.set_data(&mut out, &mut gen); + assert_eq!(out, [2, 2, 2]); + assert_eq!(n, 3); + assert_eq!(buf.get_pos(), 0); +} + +#[test] +#[rustfmt::skip] +fn test_eager_paddings() { + let mut buf_be = EagerBuffer::::new(&[0x42]); + let mut buf_le = buf_be.clone(); + let mut out_be = Vec::::new(); + let mut out_le = Vec::::new(); + let len = 0x0001_0203_0405_0607; + buf_be.len64_padding_be(len, |block| out_be.extend(block)); + buf_le.len64_padding_le(len, |block| out_le.extend(block)); + + assert_eq!( + out_be, + [ + 0x42, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + ], + ); + assert_eq!( + out_le, + [ + 0x42, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, + ], + ); + + let mut buf_be = EagerBuffer::::new(&[0x42]); + let mut buf_le = buf_be.clone(); + let mut out_be = Vec::::new(); + let mut out_le = Vec::::new(); + buf_be.len64_padding_be(len, |block| out_be.extend(block)); + buf_le.len64_padding_le(len, |block| out_le.extend(block)); + + assert_eq!( + out_be, + [0x42, 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], + ); + assert_eq!( + out_le, + [0x42, 0x80, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00], + ); + + let mut buf = EagerBuffer::::new(&[0x42]); + let mut out = Vec::::new(); + let len = 0x0001_0203_0405_0607_0809_0a0b_0c0d_0e0f; + buf.len128_padding_be(len, |block| out.extend(block)); + assert_eq!( + out, + [ + 0x42, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + ], + ); + + let mut buf = EagerBuffer::::new(&[0x42]); + let mut out = Vec::::new(); + let len = 0x0001_0203_0405_0607_0809_0a0b_0c0d_0e0f; + buf.len128_padding_be(len, |block| out.extend(block)); + assert_eq!( + out, + [ + 0x42, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + ], + ); + + let mut buf = EagerBuffer::::new(&[0x42]); + let mut out = Vec::::new(); + buf.digest_pad(0xff, &[0x10, 0x11, 0x12], |block| out.extend(block)); + assert_eq!( + out, + [0x42, 0xff, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12], + ); + + let mut buf = EagerBuffer::::new(&[0x42]); + let mut out = Vec::::new(); + buf.digest_pad(0xff, &[0x10, 0x11], |block| out.extend(block)); + assert_eq!( + out, + [0x42, 0xff, 0x10, 0x11], + ); +} diff --git a/block-padding/CHANGELOG.md b/block-padding/CHANGELOG.md index 6d3295a7..d2262996 100644 --- a/block-padding/CHANGELOG.md +++ b/block-padding/CHANGELOG.md @@ -4,11 +4,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 0.3.0 (2020-12-08) +## 0.3.2 (2022-03-10) +### Fixed +- Potential unsoundness for incorrect `Padding` implementations ([#748]) + +[#748]: https://github.com/RustCrypto/utils/pull/748 + +## 0.3.1 (2022-02-10) [YANKED] +### Fixed +- Fix doc build on docs.rs by optionally enabling the `doc_cfg` feature ([#733]) + +[#733]: https://github.com/RustCrypto/utils/pull/733 + +## 0.3.0 (2022-02-10) [YANKED] +### Added +- `Iso10126` padding algorithm ([#643]) +- `PadType` enum, `Padding::TYPE` associated constant, and `Padding::unpad_blocks` method ([#675]) + ### Changed - The `Padding` trait methods now work with blocks instead of byte slices. ([#113]) +- Bump MSRV to 1.56 and edition to 2021 ([#675]) [#113]: https://github.com/RustCrypto/utils/pull/113 +[#643]: https://github.com/RustCrypto/utils/pull/643 +[#675]: https://github.com/RustCrypto/utils/pull/675 ## 0.2.1 (2020-08-14) ### Added diff --git a/block-padding/Cargo.toml b/block-padding/Cargo.toml index 4b7051b8..7ea62c89 100644 --- a/block-padding/Cargo.toml +++ b/block-padding/Cargo.toml @@ -1,17 +1,26 @@ [package] name = "block-padding" -version = "0.3.0-pre" # Also update html_root_url in lib.rs when bumping this +version = "0.3.2" # Also update html_root_url in lib.rs when bumping this +description = "Padding and unpadding of messages divided into blocks." authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" -description = "Padding and unpadding of messages divided into blocks." +edition = "2021" +rust-version = "1.56" documentation = "https://docs.rs/block-padding" repository = "https://github.com/RustCrypto/utils" keywords = ["padding", "pkcs7", "ansix923", "iso7816"] categories = ["cryptography", "no-std"] -edition = "2018" + +# Hack to allow this crate to coexist with pre-2021 edition crates +[workspace] +members = ["."] [dependencies] generic-array = "0.14" [features] std = [] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/block-padding/src/lib.rs b/block-padding/src/lib.rs index 20698d7b..2daa3ea2 100644 --- a/block-padding/src/lib.rs +++ b/block-padding/src/lib.rs @@ -5,11 +5,11 @@ //! of the box. #![no_std] #![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/block-padding/0.3.0" + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_root_url = "https://docs.rs/block-padding/0.3.2" )] -#![forbid(unsafe_code)] +#![cfg_attr(docsrs, feature(doc_cfg))] #![warn(missing_docs, rust_2018_idioms)] #[cfg(feature = "std")] @@ -19,11 +19,25 @@ use core::fmt; pub use generic_array; use generic_array::{ArrayLength, GenericArray}; +/// Padding types +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PadType { + /// Reversible padding + Reversible, + /// Ambiguous padding + Ambiguous, + /// No padding, message must be mutliple of block size + NoPadding, +} + /// Block size. pub type Block = GenericArray; /// Trait for padding messages divided into blocks pub trait Padding> { + /// Padding type + const TYPE: PadType; + /// Pads `block` filled with data up to `pos` (i.e length of a message /// stored in the block is equal to `pos`). /// @@ -36,6 +50,28 @@ pub trait Padding> { /// /// Returns `Err(UnpadError)` if the block containts malformed padding. fn unpad(block: &Block) -> Result<&[u8], UnpadError>; + + /// Unpad data in the `blocks`. + /// + /// Returns `Err(UnpadError)` if the block containts malformed padding. + fn unpad_blocks(blocks: &[Block]) -> Result<&[u8], UnpadError> { + let bs = BlockSize::USIZE; + let res_len = match (blocks.last(), Self::TYPE) { + (_, PadType::NoPadding) => bs * blocks.len(), + (Some(last_block), _) => { + let n = Self::unpad(last_block)?.len(); + assert!(n <= bs); + n + bs * (blocks.len() - 1) + } + (None, PadType::Ambiguous) => 0, + (None, PadType::Reversible) => return Err(UnpadError), + }; + // SAFETY: `res_len` is always smaller or equal to `bs * blocks.len()` + Ok(unsafe { + let p = blocks.as_ptr() as *const u8; + core::slice::from_raw_parts(p, res_len) + }) + } } /// Pad block with zeros. @@ -60,6 +96,8 @@ pub trait Padding> { pub struct ZeroPadding; impl> Padding for ZeroPadding { + const TYPE: PadType = PadType::Ambiguous; + #[inline] fn pad(block: &mut Block, pos: usize) { if pos > B::USIZE { @@ -101,7 +139,29 @@ impl> Padding for ZeroPadding { #[derive(Clone, Copy, Debug)] pub struct Pkcs7; +impl Pkcs7 { + #[inline] + fn unpad>(block: &Block, strict: bool) -> Result<&[u8], UnpadError> { + // TODO: use bounds to check it at compile time + if B::USIZE > 255 { + panic!("block size is too big for PKCS#7"); + } + let bs = B::USIZE; + let n = block[bs - 1]; + if n == 0 || n as usize > bs { + return Err(UnpadError); + } + let s = bs - n as usize; + if strict && block[s..bs - 1].iter().any(|&v| v != n) { + return Err(UnpadError); + } + Ok(&block[..s]) + } +} + impl> Padding for Pkcs7 { + const TYPE: PadType = PadType::Reversible; + #[inline] fn pad(block: &mut Block, pos: usize) { // TODO: use bounds to check it at compile time @@ -119,20 +179,43 @@ impl> Padding for Pkcs7 { #[inline] fn unpad(block: &Block) -> Result<&[u8], UnpadError> { - // TODO: use bounds to check it at compile time - if B::USIZE > 255 { - panic!("block size is too big for PKCS#7"); - } - let bs = B::USIZE; - let n = block[bs - 1]; - if n == 0 || n as usize > bs { - return Err(UnpadError); - } - let s = bs - n as usize; - if block[s..bs - 1].iter().any(|&v| v != n) { - return Err(UnpadError); - } - Ok(&block[..s]) + Pkcs7::unpad(block, true) + } +} + +/// Pad block with arbitrary bytes ending with value equal to the number of bytes added. +/// +/// A variation of PKCS#7 that is less strict when decoding. +/// +/// ``` +/// use block_padding::{Iso10126, Padding}; +/// use generic_array::{GenericArray, typenum::U8}; +/// +/// let msg = b"test"; +/// let pos = msg.len(); +/// let mut block: GenericArray:: = [0xff; 8].into(); +/// block[..pos].copy_from_slice(msg); +/// Iso10126::pad(&mut block, pos); +/// assert_eq!(&block[..], b"test\x04\x04\x04\x04"); +/// let res = Iso10126::unpad(&block).unwrap(); +/// assert_eq!(res, msg); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Iso10126; + +impl> Padding for Iso10126 { + const TYPE: PadType = PadType::Reversible; + + #[inline] + fn pad(block: &mut Block, pos: usize) { + // Instead of generating random bytes as specified by Iso10126 we + // simply use Pkcs7 padding. + Pkcs7::pad(block, pos) + } + + #[inline] + fn unpad(block: &Block) -> Result<&[u8], UnpadError> { + Pkcs7::unpad(block, false) } } @@ -156,6 +239,8 @@ impl> Padding for Pkcs7 { pub struct AnsiX923; impl> Padding for AnsiX923 { + const TYPE: PadType = PadType::Reversible; + #[inline] fn pad(block: &mut Block, pos: usize) { // TODO: use bounds to check it at compile time @@ -210,6 +295,8 @@ impl> Padding for AnsiX923 { pub struct Iso7816; impl> Padding for Iso7816 { + const TYPE: PadType = PadType::Reversible; + #[inline] fn pad(block: &mut Block, pos: usize) { if pos >= B::USIZE { @@ -259,6 +346,8 @@ impl> Padding for Iso7816 { pub struct NoPadding; impl> Padding for NoPadding { + const TYPE: PadType = PadType::NoPadding; + #[inline] fn pad(_block: &mut Block, pos: usize) { if pos > B::USIZE { @@ -283,4 +372,5 @@ impl fmt::Display for UnpadError { } #[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for UnpadError {} diff --git a/cmov/CHANGELOG.md b/cmov/CHANGELOG.md new file mode 100644 index 00000000..ccb98bf2 --- /dev/null +++ b/cmov/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.1.1 (2022-03-02) +### Added +- `cmovz`/`cmovnz`-alike support for AArch64 targets ([#744]) + +[#744]: https://github.com/RustCrypto/utils/pull/744 + +## 0.1.0 (2022-02-27) +- Initial release diff --git a/cmov/Cargo.toml b/cmov/Cargo.toml new file mode 100644 index 00000000..fde85aed --- /dev/null +++ b/cmov/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "cmov" +description = """ +Conditional move CPU intrinsics which are guaranteed to execute in +constant-time and not be rewritten as branches by the compiler. +Provides wrappers for the CMOV family of instructions on x86/x86_64 +and CSEL on AArch64. +""" +version = "0.1.1" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/utils/tree/master/cmov" +categories = ["cryptography", "hardware-support", "no-std"] +keywords = ["crypto", "intrinsics"] +readme = "README.md" +edition = "2018" # Can't bump to 2021 due to pre-1.56 MSRV crates in the same workspace +rust-version = "1.59" diff --git a/cmov/LICENSE-APACHE b/cmov/LICENSE-APACHE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/cmov/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cmov/LICENSE-MIT b/cmov/LICENSE-MIT new file mode 100644 index 00000000..b5a814b5 --- /dev/null +++ b/cmov/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cmov/README.md b/cmov/README.md new file mode 100644 index 00000000..a708126f --- /dev/null +++ b/cmov/README.md @@ -0,0 +1,91 @@ +# [RustCrypto]: Conditional Move Intrinsics + +[![Crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache 2.0/MIT Licensed][license-image] +![MSRV][msrv-image] + +Conditional move CPU intrinsics which are guaranteed to execute in +constant-time and not be rewritten as branches by the compiler. + +Provides wrappers for the [CMOV family] of instructions on x86/x86_64 and +the [CSEL] instruction on AArch64 CPUs. + +[Documentation][docs-link] + +## About + +Conditional move intrinsics provide [predication] which allows selection +of one or more values without using branch instructions, thus making the +selection constant-time with respect to the values, and not subject to CPU +execution features which might introduce timing or other microarchitectural +sidechannels introduced by branch prediction or other speculative execution +features. + +Intel has confirmed that all extant CPUs implement the CMOV family of +instructions in constant-time, and that this property will hold for future +Intel CPUs as well. + +This crate provides wrappers for the CMOV/CSEL instructions implemented using +inline assembly as stabilized in Rust 1.59. This means the implementation +is a black box that will not be rewritten by e.g. LLVM's architecture-specific +lowerings, such as the [x86-cmov-conversion] pass. + +## Supported target architectures + +This crate provides guaranteed constant-time operation using inline assembly +on the following CPU architectures: + +- [x] `x86` (`CMOVZ`, `CMOVNZ`) +- [x] `x86_64` (`CMOVZ`, `CMOVNZ`) +- [x] `aarch64` (`CSEL`) + +On other target architectures, a "best effort" portable fallback implementation +based on bitwise arithmetic is used instead. However, we cannot guarantee that +this implementation generates branch-free code. + +It's possible to extend constant-time guarantees to other CPU architectures. +Please open an issue with your desired CPU architecture if this interests you. + +## Minimum Supported Rust Version + +Rust **1.59** or newer. + +In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope +for this crate's SemVer guarantees), however when we do it will be accompanied by +a minor version bump. + +## License + +Licensed under either of: + +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/cmov.svg +[crate-link]: https://crates.io/crates/cmov +[docs-image]: https://docs.rs/cmov/badge.svg +[docs-link]: https://docs.rs/cmov/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[msrv-image]: https://img.shields.io/badge/rustc-1.59+-blue.svg +[build-image]: https://github.com/RustCrypto/utils/actions/workflows/cmov.yml/badge.svg +[build-link]: https://github.com/RustCrypto/utils/actions/workflows/cmov.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[CMOV family]: https://www.jaist.ac.jp/iscenter-new/mpc/altix/altixdata/opt/intel/vtune/doc/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc35.htm +[CSEL]: https://developer.arm.com/documentation/dui0802/b/CSEL +[predication]: https://en.wikipedia.org/wiki/Predication_(computer_architecture) +[x86-cmov-conversion]: https://dsprenkels.com/cmov-conversion.html diff --git a/cmov/src/lib.rs b/cmov/src/lib.rs new file mode 100644 index 00000000..c72cda99 --- /dev/null +++ b/cmov/src/lib.rs @@ -0,0 +1,151 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" +)] +#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] + +#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))] +use core::arch::asm; + +/// Move if zero. +/// +/// Uses a `test` instruction to check if the given `condition` value is +/// equal to zero, then calls `cmovz` (a.k.a. `cmove`) to conditionally move +/// `src` to `dst` when `condition` is equal to zero. +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[inline(always)] +pub fn cmovz(condition: usize, src: usize, dst: &mut usize) { + unsafe { + asm! { + "test {0}, {0}", + "cmovz {1}, {2}", + in(reg) condition, + inlateout(reg) *dst, + in(reg) src, + options(pure, nomem, nostack), + }; + } +} + +/// Move if zero. +/// +/// Uses a `cmp` instruction to check if the given `condition` value is +/// equal to zero, then calls `csel` to conditionally move +/// `src` to `dst` when `condition` is equal to zero. +#[cfg(any(target_arch = "aarch64"))] +#[inline(always)] +pub fn cmovz(condition: usize, src: usize, dst: &mut usize) { + unsafe { + asm! { + "cmp {0}, 0", + "csel {1}, {2}, {3}, EQ", + in(reg) condition, + inlateout(reg) *dst, + in(reg) src, + in(reg) *dst, + options(pure, nomem, nostack), + }; + } +} + +/// Move if not zero. +/// +/// Uses a `cmp` instruction to check if the given `condition` value is not +/// equal to zero, then calls `csel` to conditionally move +/// `src` to `dst` when `condition` is nonzero. +#[cfg(any(target_arch = "aarch64"))] +#[inline(always)] +pub fn cmovnz(condition: usize, src: usize, dst: &mut usize) { + unsafe { + asm! { + "cmp {0}, 0", + "csel {1}, {2}, {3}, NE", + in(reg) condition, + inlateout(reg) *dst, + in(reg) src, + in(reg) *dst, + options(pure, nomem, nostack), + }; + } +} + +/// Move if not zero. +/// +/// Uses a `test` instruction to check if the given `condition` value is not +/// equal to zero, then calls `cmovnz` (a.k.a. `cmovne`) to conditionally move +/// `src` to `dst` when `condition` is nonzero. +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[inline(always)] +pub fn cmovnz(condition: usize, src: usize, dst: &mut usize) { + unsafe { + asm! { + "test {0}, {0}", + "cmovnz {1}, {2}", + in(reg) condition, + inlateout(reg) *dst, + in(reg) src, + options(pure, nomem, nostack), + }; + } +} + +/// Move if zero (portable fallback implementation). +/// +/// This implementation is based on portable bitwise arithmetic but cannot +/// guarantee that the resulting generated assembly is free of branch +/// instructions. +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] +#[inline(never)] +pub fn cmovz(condition: usize, src: usize, dst: &mut usize) { + let mask = (1 ^ is_non_zero(condition)).wrapping_sub(1); + *dst = (*dst & mask) | (src & !mask); +} + +/// Move if not zero (portable fallback implementation). +/// +/// This implementation is based on portable bitwise arithmetic but cannot +/// guarantee that the resulting generated assembly is free of branch +/// instructions. +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] +#[inline(never)] +pub fn cmovnz(condition: usize, src: usize, dst: &mut usize) { + let mask = is_non_zero(condition).wrapping_sub(1); + *dst = (*dst & mask) | (src & !mask); +} + +/// Check if the given condition value is non-zero +/// +/// # Returns +/// - `condition` is zero: `0` +/// - `condition` is non-zero: `1` +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))] +#[inline(always)] +fn is_non_zero(condition: usize) -> usize { + const SHIFT_BITS: usize = core::mem::size_of::() - 1; + ((condition | (!condition).wrapping_add(1)) >> SHIFT_BITS) & 1 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cmovz_works() { + let mut n = 24; + cmovz(42, 42, &mut n); + assert_eq!(n, 24); + cmovz(0, 42, &mut n); + assert_eq!(n, 42); + } + + #[test] + fn cmovnz_works() { + let mut n = 24; + cmovnz(0, 42, &mut n); + assert_eq!(n, 24); + cmovnz(42, 42, &mut n); + assert_eq!(n, 42); + } +} diff --git a/collectable/src/lib.rs b/collectable/src/lib.rs index de6cb4c1..69b1c8e4 100644 --- a/collectable/src/lib.rs +++ b/collectable/src/lib.rs @@ -5,8 +5,8 @@ #![no_std] #![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", html_root_url = "https://docs.rs/collectable/0.0.2" )] diff --git a/const-oid/CHANGELOG.md b/const-oid/CHANGELOG.md deleted file mode 100644 index 205a605c..00000000 --- a/const-oid/CHANGELOG.md +++ /dev/null @@ -1,137 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.6.0 (2021-06-03) -### Changed -- Modernize and remove deprecations; MSRV 1.51+ ([#458]) - -[#458]: https://github.com/RustCrypto/utils/pull/458 - -## 0.5.2 (2021-04-20) -### Added -- Expand README.md ([#376]) - -[#376]: https://github.com/RustCrypto/utils/pull/376 - -## 0.5.1 (2021-04-15) -### Added -- `ObjectIdentifier::MAX_LENGTH` constant ([#372]) - -### Changed -- Deprecate `ObjectIdentifier::max_len()` function ([#372]) - -[#372]: https://github.com/RustCrypto/utils/pull/372 - -## 0.5.0 (2021-03-21) -### Added -- `TryFrom<&[u8]>` impl on `ObjectIdentifier` ([#338]) - -## Changed -- MSRV 1.47+ ([#338]) -- Renamed the following methods ([#338]): - - `ObjectIdentifier::new` => `ObjectIdentifier::from_arcs` - - `ObjectIdentifier::parse` => `ObjectIdentifier::new` - - `ObjectIdentifier::from_ber` => `ObjectIdentifier::from_bytes` - -### Removed -- Deprecated methods ([#338]) -- `alloc` feature - only used by aforementioned deprecated methods ([#338]) -- `TryFrom<&[Arc]>` impl on `ObjectIdentifier` - use `::from_arcs` ([#338]) - -[#338]: https://github.com/RustCrypto/utils/pull/338 - -## 0.4.5 (2021-03-04) -### Added -- `Hash` and `Ord` impls on `ObjectIdentifier` ([#323]) - -[#323]: https://github.com/RustCrypto/utils/pull/323 - -## 0.4.4 (2021-02-28) -### Added -- `ObjectIdentifier::as_bytes` method ([#317]) - -### Changed -- Internal representation changed to BER/DER ([#317]) -- Deprecated `ObjectIdentifier::ber_len`, `::write_ber`, and `::to_ber` ([#317]) - -[#317]: https://github.com/RustCrypto/utils/pull/317 - -## 0.4.3 (2021-02-24) -### Added -- Const-friendly OID string parser ([#312]) - -[#312]: https://github.com/RustCrypto/utils/pull/312 - -## 0.4.2 (2021-02-19) -### Fixed -- Bug in root arc calculation ([#284]) - -[#284]: https://github.com/RustCrypto/utils/pull/284 - -## 0.4.1 (2020-12-21) -### Fixed -- Bug in const initializer ([#172]) - -[#172]: https://github.com/RustCrypto/utils/pull/172 - -## 0.4.0 (2020-12-16) -### Added -- `Arcs` iterator ([#141], [#142]) - -### Changed -- Rename "nodes" to "arcs" ([#142]) -- Layout optimization ([#143]) -- Refactor and improve length limits ([#144]) - -[#144]: https://github.com/RustCrypto/utils/pull/144 -[#143]: https://github.com/RustCrypto/utils/pull/143 -[#142]: https://github.com/RustCrypto/utils/pull/142 -[#141]: https://github.com/RustCrypto/utils/pull/141 - -## 0.3.5 (2020-12-12) -### Added -- `ObjectIdentifier::{write_ber, to_ber}` methods ([#118]) - -[#118]: https://github.com/RustCrypto/utils/pull/118 - -## 0.3.4 (2020-12-06) -### Changed -- Documentation improvements ([#112]) - -[#112]: https://github.com/RustCrypto/utils/pull/110 - -## 0.3.3 (2020-12-05) -### Changed -- Improve description in Cargo.toml/README.md (#110) - -[#110]: https://github.com/RustCrypto/utils/pull/110 - -## 0.3.2 (2020-12-05) -### Changed -- Documentation improvements ([#107]) - -[#107]: https://github.com/RustCrypto/utils/pull/107 - -## 0.3.1 (2020-12-05) -### Added -- Impl `TryFrom<&[u32]>` for ObjectIdentifier ([#105]) - -[#105]: https://github.com/RustCrypto/utils/pull/105 - -## 0.3.0 (2020-12-05) [YANKED] -### Added -- Byte and string parsers ([#89]) - -[#89]: https://github.com/RustCrypto/utils/pull/89 - -## 0.2.0 (2020-09-05) -### Changed -- Validate OIDs are well-formed; MSRV 1.46+ ([#76]) - -[#76]: https://github.com/RustCrypto/utils/pull/76 - -## 0.1.0 (2020-08-04) -- Initial release diff --git a/const-oid/Cargo.toml b/const-oid/Cargo.toml deleted file mode 100644 index 18419184..00000000 --- a/const-oid/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "const-oid" -version = "0.6.0" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -description = """ -Const-friendly implementation of the ISO/IEC Object Identifier (OID) standard -as defined in ITU X.660, with support for BER/DER encoding/decoding as well as -heapless no_std (i.e. embedded) support -""" -documentation = "https://docs.rs/const-oid" -repository = "https://github.com/RustCrypto/utils" -categories = ["cryptography", "data-structures", "encoding", "no-std", "parser-implementations"] -keywords = ["iso", "iec", "itu", "oid"] -readme = "README.md" - -[dev-dependencies] -hex-literal = "0.3" - -[features] -std = [] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/const-oid/LICENSE-APACHE b/const-oid/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/const-oid/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/const-oid/README.md b/const-oid/README.md deleted file mode 100644 index 9be5323a..00000000 --- a/const-oid/README.md +++ /dev/null @@ -1,88 +0,0 @@ -# RustCrypto: Object Identifiers (OIDs) - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Const-friendly implementation of the ISO/IEC Object Identifier (OID) standard -as defined in ITU [X.660], with support for BER/DER encoding/decoding as well -as heapless `no_std` (i.e. embedded) environments. - -[Documentation][docs-link] - -## About OIDs - -Object Identifiers, a.k.a. OIDs, are an International Telecommunications -Union (ITU) and ISO/IEC standard for naming any object, concept, or "thing" -with a globally unambiguous persistent name. - -The ITU's [X.660] standard provides the OID specification. Every OID is part of -a hierarchical namespace which begins with a *root OID*, which is either the -ITU's root OID (0), the ISO's root OID (1), or the joint ISO/ITU root OID (2). - -The following is an example of an OID, in this case identifying the -`rsaEncryption` algorithm: - -```text -1.2.840.113549.1.1.1 -``` - -For more information, see: - -## Implementation - -This library supports parsing OIDs in const contexts, e.g.: - -```rust -use const_oid::ObjectIdentifier; - -pub const MY_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.1"); -``` - -The OID parser is implemented entirely in terms of `const fn` and without the -use of proc macros. - -Additionally, it also includes a `const fn` OID serializer, and stores the OIDs -parsed from const contexts encoded using the BER/DER serialization -(sans header). - -This allows `ObjectIdentifier` to impl `AsRef<[u8]>` which can be used to -obtain the BER/DER serialization of an OID, even one declared `const`. - -Additionally, it impls `FromStr` and `TryFrom<&[u8]>` and functions just as -well as a runtime OID library. - -## License - -Licensed under either of: - -* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) -* [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/const-oid.svg -[crate-link]: https://crates.io/crates/const-oid -[docs-image]: https://docs.rs/const-oid/badge.svg -[docs-link]: https://docs.rs/const-oid/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/const-oid/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions - -[//]: # (general links) - -[X.660]: https://www.itu.int/rec/T-REC-X.660 diff --git a/const-oid/src/arcs.rs b/const-oid/src/arcs.rs deleted file mode 100644 index 5041021f..00000000 --- a/const-oid/src/arcs.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! Arcs are integer values which exist within an OID's hierarchy. - -use crate::{Error, ObjectIdentifier, Result}; -use core::convert::TryFrom; - -/// Type used to represent an "arc" (i.e. integer identifier value) -pub type Arc = u32; - -/// Maximum value of the first arc in an OID -pub(crate) const FIRST_ARC_MAX: Arc = 2; - -/// Maximum value of the second arc in an OID -pub(crate) const SECOND_ARC_MAX: Arc = 39; - -/// [`Iterator`] over arcs (a.k.a. nodes) in an [`ObjectIdentifier`]. -/// -/// This iterates over all arcs in an OID, including the root. -pub struct Arcs<'a> { - /// OID we're iterating over - oid: &'a ObjectIdentifier, - - /// Current position within the serialized DER bytes of this OID - cursor: Option, -} - -impl<'a> Arcs<'a> { - /// Create a new iterator over the arcs of this OID - pub(crate) fn new(oid: &'a ObjectIdentifier) -> Self { - Self { oid, cursor: None } - } -} - -impl<'a> Iterator for Arcs<'a> { - type Item = Arc; - - fn next(&mut self) -> Option { - match self.cursor { - // Indicates we're on the root OID - None => { - let root = RootArcs(self.oid.as_bytes()[0]); - self.cursor = Some(0); - Some(root.first_arc()) - } - Some(0) => { - let root = RootArcs(self.oid.as_bytes()[0]); - self.cursor = Some(1); - Some(root.second_arc()) - } - Some(offset) => { - let mut result = 0; - let mut arc_bytes = 0; - - // TODO(tarcieri): consolidate this with `ObjectIdentifier::from_bytes`? - loop { - match self.oid.as_bytes().get(offset + arc_bytes).cloned() { - Some(byte) => { - arc_bytes += 1; - assert!( - arc_bytes < 4 || byte & 0b11110000 == 0, - "OID arc overflowed" - ); - result = result << 7 | (byte & 0b1111111) as Arc; - - if byte & 0b10000000 == 0 { - self.cursor = Some(offset + arc_bytes); - return Some(result); - } - } - None => { - assert_eq!(arc_bytes, 0, "truncated OID"); - return None; - } - } - } - } - } - } -} - -/// Byte containing the first and second arcs of an OID. -/// -/// This is represented this way in order to reduce the overall size of the -/// [`ObjectIdentifier`] struct. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub(crate) struct RootArcs(u8); - -impl RootArcs { - /// Create [`RootArcs`] from the first and second arc values represented - /// as `Arc` integers. - pub(crate) fn new(first_arc: Arc, second_arc: Arc) -> Result { - if first_arc > FIRST_ARC_MAX || second_arc > SECOND_ARC_MAX { - return Err(Error); - } - - let byte = (first_arc * (SECOND_ARC_MAX + 1)) as u8 + second_arc as u8; - Ok(Self(byte)) - } - - /// Get the value of the first arc - pub(crate) fn first_arc(self) -> Arc { - self.0 as Arc / (SECOND_ARC_MAX + 1) - } - - /// Get the value of the second arc - pub(crate) fn second_arc(self) -> Arc { - self.0 as Arc % (SECOND_ARC_MAX + 1) - } -} - -impl TryFrom for RootArcs { - type Error = Error; - - fn try_from(octet: u8) -> Result { - let first = octet as Arc / (SECOND_ARC_MAX + 1); - let second = octet as Arc % (SECOND_ARC_MAX + 1); - let result = Self::new(first, second)?; - debug_assert_eq!(octet, result.0); - Ok(result) - } -} - -impl From for u8 { - fn from(root_arcs: RootArcs) -> u8 { - root_arcs.0 - } -} diff --git a/const-oid/src/encoder.rs b/const-oid/src/encoder.rs deleted file mode 100644 index 29b3e099..00000000 --- a/const-oid/src/encoder.rs +++ /dev/null @@ -1,150 +0,0 @@ -//! OID encoder with `const` support. - -use crate::{ - arcs::{FIRST_ARC_MAX, SECOND_ARC_MAX}, - Arc, Error, ObjectIdentifier, Result, -}; - -/// BER/DER encoder -pub(crate) struct Encoder { - /// Current state - state: State, - - /// Bytes of the OID being encoded in-progress - bytes: [u8; ObjectIdentifier::MAX_LENGTH], - - /// Current position within the byte buffer - cursor: usize, -} - -/// Current state of the encoder -enum State { - /// Initial state - no arcs yet encoded - Initial, - - /// First arc parsed - FirstArc(Arc), - - /// Encoding base 128 body of the OID - Body, -} - -impl Encoder { - /// Create a new encoder initialized to an empty default state - pub(crate) const fn new() -> Self { - Self { - state: State::Initial, - bytes: [0u8; ObjectIdentifier::MAX_LENGTH], - cursor: 0, - } - } - - /// Encode an [`Arc`] as base 128 into the internal buffer - pub(crate) const fn encode(mut self, arc: Arc) -> Self { - match self.state { - State::Initial => { - const_assert!(arc <= FIRST_ARC_MAX, "invalid first arc (must be 0-2)"); - self.state = State::FirstArc(arc); - self - } - State::FirstArc(first_arc) => { - const_assert!(arc <= SECOND_ARC_MAX, "invalid second arc (must be 0-39)"); - self.state = State::Body; - self.bytes[0] = (first_arc * (SECOND_ARC_MAX + 1)) as u8 + arc as u8; - self.cursor = 1; - self - } - State::Body => { - // Total number of bytes in encoded arc - 1 - let nbytes = base128_len(arc); - - const_assert!( - self.cursor + nbytes + 1 < ObjectIdentifier::MAX_LENGTH, - "OID too long (exceeded max DER bytes)" - ); - - let new_cursor = self.cursor + nbytes + 1; - let mut result = self.encode_base128_byte(arc, nbytes, false); - result.cursor = new_cursor; - result - } - } - } - - /// Finish encoding an OID - pub(crate) const fn finish(self) -> ObjectIdentifier { - const_assert!(self.cursor >= 2, "OID too short (minimum 3 arcs)"); - ObjectIdentifier { - bytes: self.bytes, - length: self.cursor as u8, - } - } - - /// Encode a single byte of a base128 value - const fn encode_base128_byte(mut self, mut n: u32, i: usize, continued: bool) -> Self { - let mask = if continued { 0b10000000 } else { 0 }; - - if n > 0x80 { - self.bytes[self.cursor + i] = (n & 0b1111111) as u8 | mask; - n >>= 7; - - const_assert!(i > 0, "Base 128 offset miscalculation"); - self.encode_base128_byte(n, i.saturating_sub(1), true) - } else { - self.bytes[self.cursor] = n as u8 | mask; - self - } - } -} - -/// Compute the length - 1 of an arc when encoded in base 128 -const fn base128_len(arc: Arc) -> usize { - match arc { - 0..=0x7f => 0, - 0x80..=0x3fff => 1, - 0x4000..=0x1fffff => 2, - 0x200000..=0x1fffffff => 3, - _ => 4, - } -} - -/// Write the given unsigned integer in base 128 -// TODO(tarcieri): consolidate encoding logic with `encode_base128_byte` -pub(crate) fn write_base128(bytes: &mut [u8], mut n: Arc) -> Result { - let nbytes = base128_len(n); - let mut i = nbytes; - let mut mask = 0; - - while n > 0x80 { - let byte = bytes.get_mut(i).ok_or(Error)?; - *byte = (n & 0b1111111 | mask) as u8; - n >>= 7; - i = i.checked_sub(1).expect("overflow"); - mask = 0b10000000; - } - - bytes[0] = (n | mask) as u8; - - Ok(nbytes + 1) -} - -#[cfg(test)] -mod tests { - use super::Encoder; - use hex_literal::hex; - - /// OID `1.2.840.10045.2.1` encoded as ASN.1 BER/DER - const EXAMPLE_OID_BER: &[u8] = &hex!("2A8648CE3D0201"); - - #[test] - fn encode() { - let encoder = Encoder::new(); - let encoder = encoder.encode(1); - let encoder = encoder.encode(2); - let encoder = encoder.encode(840); - let encoder = encoder.encode(10045); - let encoder = encoder.encode(2); - let encoder = encoder.encode(1); - assert_eq!(&encoder.bytes[..encoder.cursor], EXAMPLE_OID_BER); - } -} diff --git a/const-oid/src/error.rs b/const-oid/src/error.rs deleted file mode 100644 index 01ddb723..00000000 --- a/const-oid/src/error.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Error types - -use core::fmt; - -/// Result type -pub type Result = core::result::Result; - -/// Error type -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Error; - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("OID error") - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} diff --git a/const-oid/src/lib.rs b/const-oid/src/lib.rs deleted file mode 100644 index 20f2dbb8..00000000 --- a/const-oid/src/lib.rs +++ /dev/null @@ -1,299 +0,0 @@ -//! Const-friendly X.660 Object Identifier (OID) library with support for -//! heapless `no_std` (i.e. embedded) environments. -//! -//! # About OIDs -//! -//! Object Identifiers (a.k.a. OIDs, represented by this library as the -//! [`ObjectIdentifier`] struct) are an International Telecommunications Union -//! (ITU) and ISO/IEC standard for naming any object, concept, or "thing" -//! with a globally unambiguous persistent name. -//! -//! OIDS are defined in the ITU's [X.660] standard. -//! -//! The following is an example of an OID, in this case identifying the -//! `rsaEncryption` algorithm: -//! -//! ```text -//! 1.2.840.113549.1.1.1 -//! ``` -//! -//! For more information, see: -//! -//! ## Implementation -//! -//! This library supports parsing OIDs in const contexts, e.g.: -//! -//! ```rust -//! use const_oid::ObjectIdentifier; -//! -//! pub const MY_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.1"); -//! ``` -//! -//! The OID parser is implemented entirely in terms of `const fn` and without the -//! use of proc macros. -//! -//! Additionally, it also includes a `const fn` OID serializer, and stores the OIDs -//! parsed from const contexts encoded using the BER/DER serialization -//! (sans header). -//! -//! This allows [`ObjectIdentifier`] to impl `AsRef<[u8]>` which can be used to -//! obtain the BER/DER serialization of an OID, even one declared `const`. -//! -//! Additionally, it impls `FromStr` and `TryFrom<&[u8]>` and functions just as -//! well as a runtime OID library. -//! -//! # Minimum Supported Rust Version -//! -//! This crate requires **Rust 1.51** at a minimum. -//! -//! Minimum supported Rust version may be changed in the future, but it will be -//! accompanied with a minor version bump. -//! -//! [X.660]: https://www.itu.int/rec/T-REC-X.660 - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/const-oid/0.6.0" -)] -#![forbid(unsafe_code, clippy::unwrap_used)] -#![warn(missing_docs, rust_2018_idioms)] - -#[cfg(feature = "std")] -extern crate std; - -#[macro_use] -mod macros; - -mod arcs; -mod encoder; -mod error; -mod parser; - -pub use crate::{ - arcs::{Arc, Arcs}, - error::{Error, Result}, -}; - -use crate::arcs::RootArcs; -use core::{convert::TryFrom, fmt, str::FromStr}; - -/// Object identifier (OID). -/// -/// OIDs are hierarchical structures consisting of "arcs", i.e. integer -/// identifiers. -/// -/// # Validity -/// -/// In order for an OID to be considered valid by this library, it must meet -/// the following criteria: -/// -/// - The OID MUST have at least 3 arcs -/// - The first arc MUST be within the range 0-2 -/// - The second arc MUST be within the range 0-39 -/// - The BER/DER encoding of the OID MUST be shorter than -/// [`ObjectIdentifier::max_len`] -#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub struct ObjectIdentifier { - /// Array containing BER/DER-serialized bytes (no header) - bytes: [u8; Self::MAX_LENGTH], - - /// Length in bytes - length: u8, -} - -#[allow(clippy::len_without_is_empty)] -impl ObjectIdentifier { - /// Maximum length of a BER/DER-encoded OID in bytes. - pub const MAX_LENGTH: usize = 23; // 24-bytes total w\ 1-byte length - - /// Parse an [`ObjectIdentifier`] from the dot-delimited string form, e.g.: - /// - /// ``` - /// use const_oid::ObjectIdentifier; - /// - /// pub const MY_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.1"); - /// ``` - /// - /// # Panics - /// - /// This method panics in the event the OID is malformed according to the - /// "Validity" rules given in the toplevel documentation for this type. - /// - /// For that reason this method is *ONLY* recommended for use in constants - /// (where it will generate a compiler error instead). - /// - /// To parse an OID from a `&str` slice fallibly and without panicking, - /// use the [`FromStr`][1] impl instead (or via `str`'s [`parse`][2] - /// method). - /// - /// [1]: ./struct.ObjectIdentifier.html#impl-FromStr - /// [2]: https://doc.rust-lang.org/nightly/std/primitive.str.html#method.parse - pub const fn new(s: &str) -> Self { - parser::Parser::parse(s).finish() - } - - /// Parse an OID from a slice of [`Arc`] values (i.e. integers). - pub fn from_arcs(arcs: &[Arc]) -> Result { - let mut bytes = [0u8; Self::MAX_LENGTH]; - - bytes[0] = match *arcs { - [first, second, _, ..] => RootArcs::new(first, second)?.into(), - _ => return Err(Error), - }; - - let mut offset = 1; - - for &arc in &arcs[2..] { - offset += encoder::write_base128(&mut bytes[offset..], arc)?; - } - - Ok(Self { - bytes, - length: offset as u8, - }) - } - - /// Parse an OID from from its BER/DER encoding. - pub fn from_bytes(ber_bytes: &[u8]) -> Result { - let len = ber_bytes.len(); - - if !(2..=Self::MAX_LENGTH).contains(&len) { - return Err(Error); - } - - // Validate root arcs are in range - ber_bytes - .get(0) - .cloned() - .ok_or(Error) - .and_then(RootArcs::try_from)?; - - // Validate lower arcs are well-formed - let mut arc_offset = 1; - let mut arc_bytes = 0; - - // TODO(tarcieri): consolidate this with `Arcs::next`? - while arc_offset < len { - match ber_bytes.get(arc_offset + arc_bytes).cloned() { - Some(byte) => { - arc_bytes += 1; - - if arc_bytes == 4 && byte & 0b11110000 != 0 { - // Overflowed `Arc` (u32) - return Err(Error); - } - - if byte & 0b10000000 == 0 { - arc_offset += arc_bytes; - arc_bytes = 0; - } - } - None => return Err(Error), // truncated OID - } - } - - let mut bytes = [0u8; Self::MAX_LENGTH]; - bytes[..len].copy_from_slice(ber_bytes); - - Ok(Self { - bytes, - length: len as u8, - }) - } - - /// Get the BER/DER serialization of this OID as bytes. - /// - /// Note that this encoding omits the tag/length, and only contains the - /// value portion of the encoded OID. - pub fn as_bytes(&self) -> &[u8] { - &self.bytes[..self.length as usize] - } - - /// Return the arc with the given index, if it exists. - pub fn arc(&self, index: usize) -> Option { - self.arcs().nth(index) - } - - /// Iterate over the arcs (a.k.a. nodes) of an [`ObjectIdentifier`]. - /// - /// Returns [`Arcs`], an iterator over `Arc` values representing the value - /// of each arc/node. - pub fn arcs(&self) -> Arcs<'_> { - Arcs::new(self) - } -} - -impl AsRef<[u8]> for ObjectIdentifier { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl FromStr for ObjectIdentifier { - type Err = Error; - - fn from_str(string: &str) -> Result { - let mut split = string.split('.'); - let first_arc = split.next().and_then(|s| s.parse().ok()).ok_or(Error)?; - let second_arc = split.next().and_then(|s| s.parse().ok()).ok_or(Error)?; - - let mut bytes = [0u8; Self::MAX_LENGTH]; - bytes[0] = RootArcs::new(first_arc, second_arc)?.into(); - - let mut offset = 1; - - for s in split { - let arc = s.parse().map_err(|_| Error)?; - offset += encoder::write_base128(&mut bytes[offset..], arc)?; - } - - if offset > 1 { - Ok(Self { - bytes, - length: offset as u8, - }) - } else { - // Minimum 3 arcs - Err(Error) - } - } -} - -impl TryFrom<&[u8]> for ObjectIdentifier { - type Error = Error; - - fn try_from(ber_bytes: &[u8]) -> Result { - Self::from_bytes(ber_bytes) - } -} - -impl From<&ObjectIdentifier> for ObjectIdentifier { - fn from(oid: &ObjectIdentifier) -> ObjectIdentifier { - *oid - } -} - -impl fmt::Debug for ObjectIdentifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ObjectIdentifier({})", self) - } -} - -impl fmt::Display for ObjectIdentifier { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let len = self.arcs().count(); - - for (i, arc) in self.arcs().enumerate() { - write!(f, "{}", arc)?; - - if i < len - 1 { - write!(f, ".")?; - } - } - - Ok(()) - } -} diff --git a/const-oid/src/macros.rs b/const-oid/src/macros.rs deleted file mode 100644 index 447a9f25..00000000 --- a/const-oid/src/macros.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Macro definitions - -/// Constant panicking assertion. -// TODO(tarcieri): use const panic when stable. -// See: https://github.com/rust-lang/rust/issues/51999 -macro_rules! const_assert { - ($bool:expr, $msg:expr) => { - [$msg][!$bool as usize] - }; -} diff --git a/const-oid/src/parser.rs b/const-oid/src/parser.rs deleted file mode 100644 index c0a7c52f..00000000 --- a/const-oid/src/parser.rs +++ /dev/null @@ -1,99 +0,0 @@ -//! OID string parser with `const` support. - -use crate::{encoder::Encoder, Arc, ObjectIdentifier}; - -/// Const-friendly OID string parser. -/// -/// Parses an OID from the dotted string representation. -pub(crate) struct Parser { - /// Current arc in progress - current_arc: Arc, - - /// BER/DER encoder - encoder: Encoder, -} - -impl Parser { - /// Parse an OID from a dot-delimited string e.g. `1.2.840.113549.1.1.1` - pub(crate) const fn parse(s: &str) -> Self { - let bytes = s.as_bytes(); - const_assert!(!bytes.is_empty(), "OID string is empty"); - const_assert!( - matches!(bytes[0], b'0'..=b'9'), - "OID must start with a digit" - ); - - let current_arc = 0; - let encoder = Encoder::new(); - Self { - current_arc, - encoder, - } - .parse_bytes(bytes) - } - - /// Finish parsing, returning the result - pub(crate) const fn finish(self) -> ObjectIdentifier { - self.encoder.finish() - } - - /// Parse the remaining bytes - const fn parse_bytes(mut self, bytes: &[u8]) -> Self { - match bytes { - [] => { - self.encoder = self.encoder.encode(self.current_arc); - self - } - [byte @ b'0'..=b'9', remaining @ ..] => { - let digit = byte.saturating_sub(b'0'); - self.current_arc = self.current_arc * 10 + digit as Arc; - self.parse_bytes(remaining) - } - [b'.', remaining @ ..] => { - const_assert!(!remaining.is_empty(), "invalid trailing '.' in OID"); - self.encoder = self.encoder.encode(self.current_arc); - self.current_arc = 0; - self.parse_bytes(remaining) - } - [byte, ..] => { - const_assert!( - matches!(byte, b'0'..=b'9' | b'.'), - "invalid character in OID" - ); - - // Unreachable (checked by above `const_assert!`) - // Needed for match exhaustiveness and matching types - self - } - } - } -} - -#[cfg(test)] -mod tests { - use super::Parser; - - #[test] - fn parse() { - let oid = Parser::parse("1.23.456").finish(); - assert_eq!(oid, "1.23.456".parse().unwrap()); - } - - #[test] - #[should_panic] - fn reject_empty_string() { - Parser::parse(""); - } - - #[test] - #[should_panic] - fn reject_non_digits() { - Parser::parse("X"); - } - - #[test] - #[should_panic] - fn reject_trailing_dot() { - Parser::parse("1.23."); - } -} diff --git a/const-oid/tests/lib.rs b/const-oid/tests/lib.rs deleted file mode 100644 index df23f9f6..00000000 --- a/const-oid/tests/lib.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! `const-oid` crate tests - -// TODO(tarcieri): test full set of OID encoding constraints specified here: -// - -use const_oid::ObjectIdentifier; -use hex_literal::hex; -use std::string::ToString; - -/// Example OID value with a root arc of `1` -const EXAMPLE_OID_1: ObjectIdentifier = ObjectIdentifier::new("1.2.840.10045.2.1"); - -/// Example OID value with a root arc of `2` -const EXAMPLE_OID_2: ObjectIdentifier = ObjectIdentifier::new("2.16.840.1.101.3.4.1.42"); - -/// Example OID 1 encoded as ASN.1 BER/DER -const EXAMPLE_OID_1_BER: &[u8] = &hex!("2A8648CE3D0201"); - -/// Example OID 2 encoded as ASN.1 BER/DER -const EXAMPLE_OID_2_BER: &[u8] = &hex!("60864801650304012A"); - -/// Example OID 1 as a string -const EXAMPLE_OID_1_STRING: &str = "1.2.840.10045.2.1"; - -/// Example OID 2 as a string -const EXAMPLE_OID_2_STRING: &str = "2.16.840.1.101.3.4.1.42"; - -#[test] -fn display() { - assert_eq!(EXAMPLE_OID_1.to_string(), EXAMPLE_OID_1_STRING); - assert_eq!(EXAMPLE_OID_2.to_string(), EXAMPLE_OID_2_STRING); -} - -#[test] -fn from_bytes() { - let oid1 = ObjectIdentifier::from_bytes(EXAMPLE_OID_1_BER).unwrap(); - assert_eq!(oid1.arc(0).unwrap(), 1); - assert_eq!(oid1.arc(1).unwrap(), 2); - assert_eq!(oid1, EXAMPLE_OID_1); - - let oid2 = ObjectIdentifier::from_bytes(EXAMPLE_OID_2_BER).unwrap(); - assert_eq!(oid2.arc(0).unwrap(), 2); - assert_eq!(oid2.arc(1).unwrap(), 16); - assert_eq!(oid2, EXAMPLE_OID_2); - - // Empty - assert!(ObjectIdentifier::from_bytes(&[]).is_err()); - - // Truncated - assert!(ObjectIdentifier::from_bytes(&[42]).is_err()); - assert!(ObjectIdentifier::from_bytes(&[42, 134]).is_err()); -} - -#[test] -fn from_str() { - let oid1 = EXAMPLE_OID_1_STRING.parse::().unwrap(); - assert_eq!(oid1.arc(0).unwrap(), 1); - assert_eq!(oid1.arc(1).unwrap(), 2); - assert_eq!(oid1, EXAMPLE_OID_1); - - let oid2 = EXAMPLE_OID_2_STRING.parse::().unwrap(); - assert_eq!(oid2.arc(0).unwrap(), 2); - assert_eq!(oid2.arc(1).unwrap(), 16); - assert_eq!(oid2, EXAMPLE_OID_2); - - // Too short - assert!("1.2".parse::().is_err()); - - // Truncated - assert!("1.2.840.10045.2.".parse::().is_err()); - - // Invalid first arc - assert!("3.2.840.10045.2.1".parse::().is_err()); - - // Invalid second arc - assert!("1.40.840.10045.2.1".parse::().is_err()); -} - -#[test] -fn try_from_u32_slice() { - let oid1 = ObjectIdentifier::from_arcs(&[1, 2, 840, 10045, 2, 1]).unwrap(); - assert_eq!(oid1.arc(0).unwrap(), 1); - assert_eq!(oid1.arc(1).unwrap(), 2); - assert_eq!(EXAMPLE_OID_1, oid1); - - let oid2 = ObjectIdentifier::from_arcs(&[2, 16, 840, 1, 101, 3, 4, 1, 42]).unwrap(); - assert_eq!(oid2.arc(0).unwrap(), 2); - assert_eq!(oid2.arc(1).unwrap(), 16); - assert_eq!(EXAMPLE_OID_2, oid2); - - // Too short - assert!(ObjectIdentifier::from_arcs(&[1, 2]).is_err()); - - // Invalid first arc - assert!(ObjectIdentifier::from_arcs(&[3, 2, 840, 10045, 3, 1, 7]).is_err()); - - // Invalid second arc - assert!(ObjectIdentifier::from_arcs(&[1, 40, 840, 10045, 3, 1, 7]).is_err()); -} - -#[test] -fn as_bytes() { - assert_eq!(EXAMPLE_OID_1.as_bytes(), EXAMPLE_OID_1_BER); - assert_eq!(EXAMPLE_OID_2.as_bytes(), EXAMPLE_OID_2_BER); -} - -#[test] -#[should_panic] -fn parse_empty() { - ObjectIdentifier::new(""); -} - -#[test] -#[should_panic] -fn parse_too_short() { - ObjectIdentifier::new("1.2"); -} - -#[test] -#[should_panic] -fn parse_invalid_first_arc() { - ObjectIdentifier::new("3.2.840.10045.3.1.7"); -} - -#[test] -#[should_panic] -fn parse_invalid_second_arc() { - ObjectIdentifier::new("1.40.840.10045.3.1.7"); -} diff --git a/cpufeatures/CHANGELOG.md b/cpufeatures/CHANGELOG.md index 560943cb..e2961e71 100644 --- a/cpufeatures/CHANGELOG.md +++ b/cpufeatures/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.2.2 (2022-03-18) +### Added +- Support for Android on `aarch64` ([#752]) + +### Removed +- Vestigial code around `crypto` target feature ([#600]) + +[#600]: https://github.com/RustCrypto/utils/pull/600 +[#752]: https://github.com/RustCrypto/utils/pull/752 + ## 0.2.1 (2021-08-26) ### Changed - Revert [#583] "Use from_bytes_with_nul for string check" ([#597]) diff --git a/cpufeatures/Cargo.toml b/cpufeatures/Cargo.toml index 25223602..f5ab619b 100644 --- a/cpufeatures/Cargo.toml +++ b/cpufeatures/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "cpufeatures" -version = "0.2.1" # Also update html_root_url in lib.rs when bumping this +version = "0.2.2" # Also update html_root_url in lib.rs when bumping this description = """ -Lightweight and efficient no-std compatible alternative to the -is_x86_feature_detected! macro +Lightweight runtime CPU feature detection for x86/x86_64 and aarch64 with +no_std support and support for mobile targets including Android and iOS """ authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" @@ -15,7 +15,10 @@ edition = "2018" readme = "README.md" [target.aarch64-apple-darwin.dependencies] -libc = "0.2.101" +libc = "0.2.68" [target.'cfg(all(target_arch = "aarch64", target_os = "linux"))'.dependencies] -libc = "0.2.101" +libc = "0.2.68" + +[target.aarch64-linux-android.dependencies] +libc = "0.2.68" diff --git a/cpufeatures/README.md b/cpufeatures/README.md index 29093795..5a5bb406 100644 --- a/cpufeatures/README.md +++ b/cpufeatures/README.md @@ -7,17 +7,21 @@ [![Project Chat][chat-image]][chat-link] [![Build Status][build-image]][build-link] -Lightweight and efficient `no_std` compatible alternative to the -`is_x86_feature_detected!` macro providing runtime CPU feature detection -for x86/x86_64 and ARM64 CPUs. +Lightweight and efficient runtime CPU feature detection for `aarch64` and +`x86`/`x86_64` targets. + +Supports `no_std` as well as mobile targets including iOS and Android, +providing an alternative to the `std`-dependent `is_x86_feature_detected!` +macro. [Documentation][docs-link] ## Supported architectures -### `aarch64`: Linux and macOS/M4 only +### `aarch64`: Android, iOS, Linux, and macOS/M4 only -Note: ARM64 does not support OS-independent feature detection +Note: ARM64 does not support OS-independent feature detection, so support must +be implemented on an OS-by-OS basis. Target features: diff --git a/cpufeatures/src/aarch64.rs b/cpufeatures/src/aarch64.rs index c5f9d613..6efdde1a 100644 --- a/cpufeatures/src/aarch64.rs +++ b/cpufeatures/src/aarch64.rs @@ -21,7 +21,7 @@ macro_rules! __unless_target_features { } // Linux runtime detection of target CPU features using `getauxval`. -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] #[macro_export] #[doc(hidden)] macro_rules! __detect_target_features { @@ -32,7 +32,7 @@ macro_rules! __detect_target_features { } /// Linux helper function for calling `getauxval` to get `AT_HWCAP`. -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] pub fn getauxval_hwcap() -> u64 { unsafe { libc::getauxval(libc::AT_HWCAP) } } @@ -48,7 +48,7 @@ macro_rules! __detect_target_features { } // Linux `expand_check_macro` -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] macro_rules! __expand_check_macro { ($(($name:tt, $hwcap:ident)),* $(,)?) => { #[macro_export] @@ -64,7 +64,7 @@ macro_rules! __expand_check_macro { } // Linux `expand_check_macro` -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] __expand_check_macro! { ("aes", AES), // Enable AES support. ("sha2", SHA2), // Enable SHA1 and SHA256 support. @@ -78,12 +78,11 @@ __expand_check_macro! { /// provide that mapping accordingly. /// /// See this issue for more info: -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "android"))] pub mod hwcaps { use libc::c_ulong; pub const AES: c_ulong = libc::HWCAP_AES | libc::HWCAP_PMULL; - pub const CRYPTO: c_ulong = AES | SHA2; pub const SHA2: c_ulong = libc::HWCAP_SHA2; pub const SHA3: c_ulong = libc::HWCAP_SHA3 | libc::HWCAP_SHA512; } @@ -168,7 +167,12 @@ macro_rules! check { } // On other targets, runtime CPU feature detection is unavailable -#[cfg(not(any(target_os = "ios", target_os = "linux", target_os = "macos")))] +#[cfg(not(any( + target_os = "ios", + target_os = "linux", + target_os = "android", + target_os = "macos" +)))] #[macro_export] #[doc(hidden)] macro_rules! __detect_target_features { diff --git a/cpufeatures/src/lib.rs b/cpufeatures/src/lib.rs index 96eb1a27..08b6e528 100644 --- a/cpufeatures/src/lib.rs +++ b/cpufeatures/src/lib.rs @@ -55,9 +55,8 @@ #![no_std] #![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/cpufeatures/0.2.1" + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" )] #[cfg(all(target_arch = "aarch64"))] diff --git a/crypto-bigint/CHANGELOG.md b/crypto-bigint/CHANGELOG.md deleted file mode 100644 index 85c8b558..00000000 --- a/crypto-bigint/CHANGELOG.md +++ /dev/null @@ -1,72 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.2.4 (2021-08-23) -### Added -- Expose `limb` module ([#584]) -- `[limb::Inner; LIMBS]` conversions for `UInt` ([#585]) -- Bitwise right shift support for `UInt` ([#586], [#590]) - -[#584]: https://github.com/RustCrypto/utils/pull/584 -[#585]: https://github.com/RustCrypto/utils/pull/585 -[#586]: https://github.com/RustCrypto/utils/pull/586 -[#590]: https://github.com/RustCrypto/utils/pull/590 - -## 0.2.3 (2021-08-16) -### Fixed -- `UInt::wrapping_mul` ([#563]) - -### Added -- Implement the `Hash` trait for `UInt` and `Limb` ([#579]) - -[#563]: https://github.com/RustCrypto/utils/pull/563 -[#579]: https://github.com/RustCrypto/utils/pull/579 - -## 0.2.2 (2021-06-26) [YANKED] -### Added -- `Limb::is_odd` and `UInt::is_odd` ([#505]) -- `UInt::new` ([#506]) -- `rand` feature ([#508]) - -### Changed -- Deprecate `LIMB_BYTES` constant ([#504]) -- Make `Limb`'s `Inner` value public ([#507]) - -[#504]: https://github.com/RustCrypto/utils/pull/504 -[#505]: https://github.com/RustCrypto/utils/pull/505 -[#506]: https://github.com/RustCrypto/utils/pull/506 -[#507]: https://github.com/RustCrypto/utils/pull/507 -[#508]: https://github.com/RustCrypto/utils/pull/508 - -## 0.2.1 (2021-06-21) [YANKED] -### Added -- `Limb` newtype ([#499]) -- Target-specific rustdocs ([#500]) - -[#499]: https://github.com/RustCrypto/utils/pull/499 -[#500]: https://github.com/RustCrypto/utils/pull/500 - -## 0.2.0 (2021-06-07) [YANKED] -### Added -- `ConstantTimeGreater`/`ConstantTimeLess` impls for UInt ([#459]) -- `From` conversions between `UInt` and limb arrays ([#460]) -- `zeroize` feature ([#461]) -- Additional `ArrayEncoding::ByteSize` bounds ([#462]) -- `UInt::into_limbs` ([#484]) -- `Encoding` trait ([#488]) - -### Removed -- `NumBits`/`NumBytes` traits; use `Encoding` instead ([#488]) - -[#459]: https://github.com/RustCrypto/utils/pull/459 -[#460]: https://github.com/RustCrypto/utils/pull/460 -[#461]: https://github.com/RustCrypto/utils/pull/461 -[#462]: https://github.com/RustCrypto/utils/pull/462 -[#484]: https://github.com/RustCrypto/utils/pull/484 -[#488]: https://github.com/RustCrypto/utils/pull/488 - -## 0.1.0 (2021-05-30) -- Initial release diff --git a/crypto-bigint/Cargo.toml b/crypto-bigint/Cargo.toml deleted file mode 100644 index 7db292bd..00000000 --- a/crypto-bigint/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "crypto-bigint" -version = "0.2.4" # Also update html_root_url in lib.rs when bumping this -description = """ -Pure Rust implementation of a big integer library which has been designed from -the ground-up for use in cryptographic applications. Provides constant-time, -no_std-friendly implementations of modern formulas using const generics. -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -repository = "https://github.com/RustCrypto/utils/tree/master/crypto-bigint" -categories = ["algorithms", "cryptography", "data-structures", "mathematics", "no-std"] -keywords = ["arbitrary", "crypto", "bignum", "integer", "precision"] -readme = "README.md" - -[dependencies] -generic-array = { version = "0.14", optional = true } -subtle = { version = "2.4", default-features = false } - -# optional dependencies -rand_core = { version = "0.6", optional = true } -zeroize = { version = "1", optional = true, default-features = false } - -[dev-dependencies] -hex-literal = "0.3" -rand_chacha = "0.3" - -[features] -default = ["rand"] -alloc = [] -rand = ["rand_core"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/crypto-bigint/LICENSE-APACHE b/crypto-bigint/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/crypto-bigint/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/crypto-bigint/LICENSE-MIT b/crypto-bigint/LICENSE-MIT deleted file mode 100644 index 2726e14a..00000000 --- a/crypto-bigint/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2020 The RustCrypto Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/crypto-bigint/README.md b/crypto-bigint/README.md deleted file mode 100644 index 4dcc8d05..00000000 --- a/crypto-bigint/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# [RustCrypto]: Cryptographic Big Integers - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Pure Rust implementation of a big integer library which has been designed from -the ground-up for use in cryptographic applications. - -Provides constant-time, `no_std`-friendly implementations of modern formulas -using const generics. - -[Documentation][docs-link] - -# Minimum Supported Rust Version - -**Rust 1.51** at a minimum. - -## Goals - -- No heap allocations (`no_std`-friendly) -- Constant-time by default. We may add variable-time operations in the future - but they will be secondary and explicitly marked as such. -- Leverage what is possible today with const generics on `stable` rust. -- Support `const fn` as much as possible, including decoding big integers from - bytes/hex and performing arithmetic operations on them, with the goal of - being able to compute values at compile-time. - -## Status - -This library presently provides only a baseline level of functionality. -It's new, unaudited, and may contain bugs. We recommend that it only be -used in an experimental capacity for now. - -Please see the [feature wishlist tracking ticket] for more information. - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/crypto-bigint.svg -[crate-link]: https://crates.io/crates/crypto-bigint -[docs-image]: https://docs.rs/crypto-bigint/badge.svg -[docs-link]: https://docs.rs/crypto-bigint/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/crypto-bigint/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions/workflows/crypto-bigint.yml - -[//]: # (general links) - -[RustCrypto]: https://github.com/rustcrypto -[feature wishlist tracking ticket]: https://github.com/RustCrypto/utils/issues/453 diff --git a/crypto-bigint/proptests/Cargo.toml b/crypto-bigint/proptests/Cargo.toml deleted file mode 100644 index 68d36484..00000000 --- a/crypto-bigint/proptests/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "proptests" -version = "0.0.0" -edition = "2018" -publish = false - -[lib] -path = "./lib.rs" - -[workspace] -members = ["."] - -[dependencies] -crypto-bigint = { path = ".." } -num-bigint = "0.4" -proptest = "1" diff --git a/crypto-bigint/proptests/lib.rs b/crypto-bigint/proptests/lib.rs deleted file mode 100644 index 7d046eb5..00000000 --- a/crypto-bigint/proptests/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Relevant code lives in `crypto-bigint/proptests/tests/` -//! -//! Nothing to see here! diff --git a/crypto-bigint/proptests/tests/equivalence.rs b/crypto-bigint/proptests/tests/equivalence.rs deleted file mode 100644 index 122b9c17..00000000 --- a/crypto-bigint/proptests/tests/equivalence.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! Equivalence tests between `num-bigint` and `crypto-bigint` - -use crypto_bigint::{Encoding, U256, Limb}; -use num_bigint::BigUint; -use proptest::prelude::*; -use std::mem; - -/// Example prime number (NIST P-256 curve order) -const P: U256 = - U256::from_be_hex("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551"); - -fn to_biguint(uint: &U256) -> BigUint { - BigUint::from_bytes_le(uint.to_le_bytes().as_ref()) -} - -fn to_uint(big_uint: BigUint) -> U256 { - let mut input = [0u8; U256::BYTE_SIZE]; - let encoded = big_uint.to_bytes_le(); - let l = encoded.len().min(U256::BYTE_SIZE); - input[..l].copy_from_slice(&encoded[..l]); - - U256::from_le_slice(&input) -} - -prop_compose! { - fn uint()(bytes in any::<[u8; 32]>()) -> U256 { - U256::from_le_slice(&bytes) - } -} -prop_compose! { - fn uint_mod_p(p: U256)(a in uint()) -> U256 { - // TODO: replace with mod operation on U256 - to_uint(to_biguint(&a) % to_biguint(&p)) - } -} - -proptest! { - #[test] - fn roundtrip(a in uint()) { - assert_eq!(a, to_uint(to_biguint(&a))); - } - - #[test] - fn wrapping_add(a in uint(), b in uint()) { - let a_bi = to_biguint(&a); - let b_bi = to_biguint(&b); - - let expected = to_uint(a_bi + b_bi); - let actual = a.wrapping_add(&b); - - assert_eq!(expected, actual); - } - - #[test] - fn add_mod_nist_p256(a in uint_mod_p(P), b in uint_mod_p(P)) { - assert!(a < P); - assert!(b < P); - - let a_bi = to_biguint(&a); - let b_bi = to_biguint(&b); - let p_bi = to_biguint(&P); - - let x_bi = a_bi + b_bi; - let x = a.adc(&b, Limb::ZERO).0; - assert_eq!(to_uint(x_bi.clone()), x); - - let expected = to_uint(to_biguint(&x) % p_bi); - let actual = a.add_mod(&b, &P); - - assert!(expected < P); - assert!(actual < P); - - assert_eq!(expected, actual, "{} != {} ({} + {} mod {})", expected, actual, a, b, P); - } - - - #[test] - fn sub_mod_nist_p256(mut a in uint_mod_p(P), mut b in uint_mod_p(P)) { - if b > a { - mem::swap(&mut a, &mut b); - } - - assert!(a < P); - assert!(b < P); - - let a_bi = to_biguint(&a); - let b_bi = to_biguint(&b); - let p_bi = to_biguint(&P); - - let expected = to_uint((a_bi - b_bi) % p_bi); - let actual = a.sub_mod(&b, &P); - - assert!(expected < P); - assert!(actual < P); - - assert_eq!(expected, actual); - } - - #[test] - fn wrapping_sub(mut a in uint(), mut b in uint()) { - if b > a { - mem::swap(&mut a, &mut b); - } - - let a_bi = to_biguint(&a); - let b_bi = to_biguint(&b); - - let expected = to_uint(a_bi - b_bi); - let actual = a.wrapping_sub(&b); - - assert_eq!(expected, actual); - } - - #[test] - fn wrapping_mul(a in uint(), b in uint()) { - let a_bi = to_biguint(&a); - let b_bi = to_biguint(&b); - - let expected = to_uint(a_bi * b_bi); - let actual = a.wrapping_mul(&b); - - assert_eq!(expected, actual); - } -} diff --git a/crypto-bigint/src/array.rs b/crypto-bigint/src/array.rs deleted file mode 100644 index 6e0c7fd4..00000000 --- a/crypto-bigint/src/array.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Interop support for `generic-array` - -use crate::Encoding; -use core::ops::Add; -use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; - -/// Alias for a byte array whose size is defined by [`ArrayEncoding::ByteSize`]. -#[cfg_attr(docsrs, doc(cfg(feature = "generic-array")))] -pub type ByteArray = GenericArray::ByteSize>; - -/// Support for encoding a big integer as a `GenericArray`. -#[cfg_attr(docsrs, doc(cfg(feature = "generic-array")))] -pub trait ArrayEncoding: Encoding { - /// Size of a byte array which encodes a big integer. - type ByteSize: ArrayLength + Add + Eq + Ord + Unsigned; - - /// Deserialize from a big-endian byte array. - fn from_be_byte_array(bytes: ByteArray) -> Self; - - /// Deserialize from a little-endian byte array. - fn from_le_byte_array(bytes: ByteArray) -> Self; - - /// Serialize to a big-endian byte array. - fn to_be_byte_array(&self) -> ByteArray; - - /// Serialize to a little-endian byte array. - fn to_le_byte_array(&self) -> ByteArray; -} diff --git a/crypto-bigint/src/checked.rs b/crypto-bigint/src/checked.rs deleted file mode 100644 index 951b6cb8..00000000 --- a/crypto-bigint/src/checked.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Checked arithmetic. - -use crate::UInt; -use core::ops::{Add, AddAssign, Deref, Mul, MulAssign, Sub, SubAssign}; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; - -/// Provides intentionally-checked arithmetic on `T`. -/// -/// Internally this leverages the [`CtOption`] type from the [`subtle`] crate -/// in order to handle overflows in constant time. -#[derive(Copy, Clone, Debug)] -pub struct Checked(pub CtOption); - -impl Checked { - /// Create a new checked arithmetic wrapper for the given value. - pub fn new(val: T) -> Self { - Self(CtOption::new(val, Choice::from(1))) - } -} - -impl Default for Checked -where - T: Default, -{ - fn default() -> Self { - Self::new(T::default()) - } -} - -impl Deref for Checked { - type Target = CtOption; - - fn deref(&self) -> &CtOption { - &self.0 - } -} - -impl ConditionallySelectable for Checked { - #[inline] - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - Self(CtOption::conditional_select(&a.0, &b.0, choice)) - } -} - -impl ConstantTimeEq for Checked { - #[inline] - fn ct_eq(&self, rhs: &Self) -> Choice { - self.0.ct_eq(&rhs.0) - } -} - -impl Add for Checked> { - type Output = Self; - - fn add(self, rhs: Self) -> Checked> { - Checked(self.0.and_then(|a| rhs.0.and_then(|b| a.checked_add(&b)))) - } -} - -impl AddAssign for Checked> { - fn add_assign(&mut self, other: Self) { - *self = *self + other; - } -} - -impl Sub for Checked> { - type Output = Self; - - fn sub(self, rhs: Self) -> Checked> { - Checked(self.0.and_then(|a| rhs.0.and_then(|b| a.checked_sub(&b)))) - } -} - -impl SubAssign for Checked> { - fn sub_assign(&mut self, other: Self) { - *self = *self - other; - } -} - -impl Mul for Checked> { - type Output = Self; - - fn mul(self, rhs: Self) -> Checked> { - Checked(self.0.and_then(|a| rhs.0.and_then(|b| a.checked_mul(&b)))) - } -} - -impl MulAssign for Checked> { - fn mul_assign(&mut self, other: Self) { - *self = *self * other; - } -} diff --git a/crypto-bigint/src/lib.rs b/crypto-bigint/src/lib.rs deleted file mode 100644 index 6b5526ed..00000000 --- a/crypto-bigint/src/lib.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Pure Rust implementation of a big integer library designed for cryptography. -//! -//! # About -//! This library has been designed from the ground-up for use in cryptographic -//! applications. It provides constant-time, `no_std`-friendly implementations -//! of modern formulas implemented using const generics. -//! -//! # Minimum Supported Rust Version -//! **Rust 1.51** at a minimum. -//! -//! # Goals -//! - No heap allocations i.e. `no_std`-friendly. -//! - Constant-time by default using traits from the [`subtle`] crate. -//! - Leverage what is possible today with const generics on `stable` rust. -//! - Support `const fn` as much as possible, including decoding big integers from -//! bytes/hex and performing arithmetic operations on them, with the goal of -//! being able to compute values at compile-time. -//! -//! # Status -//! This library presently provides only a baseline level of functionality. -//! It's new, unaudited, and may contain bugs. We recommend that it only be -//! used in an experimental capacity for now. -//! -//! Please see the [feature wishlist tracking ticket] for more information. -//! -//! # `generic-array` interop -//! When the optional `generic-array` feature is enabled, this library provides -//! an [`ArrayEncoding`] trait which can be used to serialize/deserialize big -//! integer values as `GenericArray`. -//! -//! [feature wishlist tracking ticket]: https://github.com/RustCrypto/utils/issues/453 - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/crypto-bigint/0.2.4" -)] -#![forbid(unsafe_code, clippy::unwrap_used)] -#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] - -#[cfg(all(feature = "alloc", test))] -extern crate alloc; - -#[macro_use] -mod macros; - -#[cfg(feature = "generic-array")] -mod array; -mod checked; -pub mod limb; -mod traits; -mod uint; -mod wrapping; - -pub use crate::{checked::Checked, limb::Limb, traits::*, uint::*, wrapping::Wrapping}; -pub use subtle; - -#[cfg(feature = "generic-array")] -pub use { - self::array::{ArrayEncoding, ByteArray}, - generic_array::{self, typenum::consts}, -}; - -/// Number of bytes in a [`Limb`]. -#[cfg(target_pointer_width = "32")] -#[deprecated(since = "0.2.2", note = "use `Limb::BYTE_SIZE` instead")] -pub const LIMB_BYTES: usize = 4; - -/// Number of bytes in a [`Limb`]. -#[cfg(target_pointer_width = "64")] -#[deprecated(since = "0.2.2", note = "use `Limb::BYTE_SIZE` instead")] -pub const LIMB_BYTES: usize = 8; diff --git a/crypto-bigint/src/limb.rs b/crypto-bigint/src/limb.rs deleted file mode 100644 index f7e3a94c..00000000 --- a/crypto-bigint/src/limb.rs +++ /dev/null @@ -1,111 +0,0 @@ -//! Big integers are represented as an array of smaller CPU word-size integers -//! called "limbs". - -#![allow(clippy::derive_hash_xor_eq)] - -mod add; -mod bit_and; -mod bit_or; -mod cmp; -mod encoding; -mod from; -mod mul; -mod sub; - -#[cfg(feature = "rand")] -mod rand; - -use core::fmt; -use subtle::{Choice, ConditionallySelectable}; - -#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] -compile_error!("this crate builds on 32-bit and 64-bit platforms only"); - -// -// 32-bit definitions -// - -/// Size of the inner integer in bits. -#[cfg(target_pointer_width = "32")] -pub const BIT_SIZE: usize = 32; - -/// Size of the inner integer in bytes. -#[cfg(target_pointer_width = "32")] -pub const BYTE_SIZE: usize = 4; - -/// Inner integer type that the [`Limb`] newtype wraps. -#[cfg(target_pointer_width = "32")] -pub type Inner = u32; - -/// Wide integer type: double the width of [`Inner`]. -#[cfg(target_pointer_width = "32")] -pub type Wide = u64; - -// -// 64-bit definitions -// - -/// Size of the inner integer in bits. -#[cfg(target_pointer_width = "64")] -pub const BIT_SIZE: usize = 64; - -/// Size of the inner integer in bytes. -#[cfg(target_pointer_width = "64")] -pub const BYTE_SIZE: usize = 8; - -/// Inner integer type that the [`Limb`] newtype wraps. -#[cfg(target_pointer_width = "64")] -pub type Inner = u64; - -/// Wide integer type: double the width of [`Inner`]. -#[cfg(target_pointer_width = "64")] -pub type Wide = u128; - -/// Big integers are represented as an array of smaller CPU word-size integers -/// called "limbs". -#[derive(Copy, Clone, Debug, Default, Hash)] -#[repr(transparent)] -pub struct Limb(pub Inner); - -impl Limb { - /// The value `0`. - pub const ZERO: Self = Limb(0); - - /// The value `1`. - pub const ONE: Self = Limb(1); - - /// Maximum value this [`Limb`] can express. - pub const MAX: Self = Limb(Inner::MAX); -} - -impl ConditionallySelectable for Limb { - #[inline] - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - Limb(Inner::conditional_select(&a.0, &b.0, choice)) - } -} - -impl fmt::Display for Limb { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::UpperHex::fmt(self, f) - } -} - -impl fmt::LowerHex for Limb { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::LowerHex::fmt(&self.0, f) - } -} - -impl fmt::UpperHex for Limb { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::UpperHex::fmt(&self.0, f) - } -} - -#[cfg(feature = "zeroize")] -#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] -impl zeroize::DefaultIsZeroes for Limb {} diff --git a/crypto-bigint/src/limb/add.rs b/crypto-bigint/src/limb/add.rs deleted file mode 100644 index 5d39341e..00000000 --- a/crypto-bigint/src/limb/add.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! Limb addition - -use super::{Inner, Limb, Wide}; -use crate::{Encoding, Wrapping}; -use core::ops::{Add, AddAssign}; -use subtle::CtOption; - -impl Limb { - /// Computes `self + rhs + carry`, returning the result along with the new carry. - #[inline(always)] - pub const fn adc(self, rhs: Limb, carry: Limb) -> (Limb, Limb) { - let a = self.0 as Wide; - let b = rhs.0 as Wide; - let carry = carry.0 as Wide; - let ret = a + b + carry; - (Limb(ret as Inner), Limb((ret >> Self::BIT_SIZE) as Inner)) - } - - /// Perform wrapping addition, discarding overflow. - #[inline(always)] - pub const fn wrapping_add(&self, rhs: Self) -> Self { - Limb(self.0.wrapping_add(rhs.0)) - } - - /// Perform checked addition, returning a [`CtOption`] which `is_some` only - /// if the operation did not overflow. - #[inline] - pub fn checked_add(&self, rhs: Self) -> CtOption { - let (result, carry) = self.adc(rhs, Limb::ZERO); - CtOption::new(result, carry.is_zero()) - } -} - -impl Add for Wrapping { - type Output = Self; - - fn add(self, rhs: Self) -> Wrapping { - Wrapping(self.0.wrapping_add(rhs.0)) - } -} - -impl Add<&Wrapping> for Wrapping { - type Output = Wrapping; - - fn add(self, rhs: &Wrapping) -> Wrapping { - Wrapping(self.0.wrapping_add(rhs.0)) - } -} - -impl Add> for &Wrapping { - type Output = Wrapping; - - fn add(self, rhs: Wrapping) -> Wrapping { - Wrapping(self.0.wrapping_add(rhs.0)) - } -} - -impl Add<&Wrapping> for &Wrapping { - type Output = Wrapping; - - fn add(self, rhs: &Wrapping) -> Wrapping { - Wrapping(self.0.wrapping_add(rhs.0)) - } -} - -impl AddAssign for Wrapping { - fn add_assign(&mut self, other: Self) { - *self = *self + other; - } -} - -impl AddAssign<&Wrapping> for Wrapping { - fn add_assign(&mut self, other: &Self) { - *self = *self + other; - } -} - -#[cfg(test)] -mod tests { - use crate::Limb; - - #[test] - fn adc_no_carry() { - let (res, carry) = Limb::ZERO.adc(Limb::ONE, Limb::ZERO); - assert_eq!(res, Limb::ONE); - assert_eq!(carry, Limb::ZERO); - } - - #[test] - fn adc_with_carry() { - let (res, carry) = Limb::MAX.adc(Limb::ONE, Limb::ZERO); - assert_eq!(res, Limb::ZERO); - assert_eq!(carry, Limb::ONE); - } - - #[test] - fn wrapping_add_no_carry() { - assert_eq!(Limb::ZERO.wrapping_add(Limb::ONE), Limb::ONE); - } - - #[test] - fn wrapping_add_with_carry() { - assert_eq!(Limb::MAX.wrapping_add(Limb::ONE), Limb::ZERO); - } - - #[test] - fn checked_add_ok() { - let result = Limb::ZERO.checked_add(Limb::ONE); - assert_eq!(result.unwrap(), Limb::ONE); - } - - #[test] - fn checked_add_overflow() { - let result = Limb::MAX.checked_add(Limb::ONE); - assert!(!bool::from(result.is_some())); - } -} diff --git a/crypto-bigint/src/limb/bit_and.rs b/crypto-bigint/src/limb/bit_and.rs deleted file mode 100644 index ff153b21..00000000 --- a/crypto-bigint/src/limb/bit_and.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Limb bit and operations. - -use super::Limb; -use core::ops::BitAnd; - -impl Limb { - /// Calculates `a & b`. - pub const fn bitand(self, rhs: Self) -> Self { - Limb(self.0 & rhs.0) - } -} - -impl BitAnd for Limb { - type Output = Limb; - - fn bitand(self, rhs: Self) -> Self::Output { - self.bitand(rhs) - } -} diff --git a/crypto-bigint/src/limb/bit_or.rs b/crypto-bigint/src/limb/bit_or.rs deleted file mode 100644 index cafac18d..00000000 --- a/crypto-bigint/src/limb/bit_or.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Limb bit or operations. - -use super::Limb; -use core::ops::BitOr; - -impl Limb { - /// Calculates `a | b`. - pub const fn bitor(self, rhs: Self) -> Self { - Limb(self.0 | rhs.0) - } -} - -impl BitOr for Limb { - type Output = Limb; - - fn bitor(self, rhs: Self) -> Self::Output { - self.bitor(rhs) - } -} diff --git a/crypto-bigint/src/limb/cmp.rs b/crypto-bigint/src/limb/cmp.rs deleted file mode 100644 index d85166fc..00000000 --- a/crypto-bigint/src/limb/cmp.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! Limb comparisons - -use super::Limb; -use core::cmp::Ordering; -use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; - -impl Limb { - /// Is this limb equal to zero? - #[inline] - pub fn is_zero(&self) -> Choice { - self.ct_eq(&Self::ZERO) - } - - /// Is this limb an odd number? - #[inline] - pub fn is_odd(&self) -> Choice { - Choice::from(self.0 as u8 & 1) - } - - /// Perform a comparison of the inner value in variable-time. - /// - /// Note that the [`PartialOrd`] and [`Ord`] impls wrap constant-time - /// comparisons using the `subtle` crate. - pub fn cmp_vartime(&self, other: &Self) -> Ordering { - self.0.cmp(&other.0) - } - - /// Performs an equality check in variable-time. - pub const fn eq_vartime(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl ConstantTimeEq for Limb { - #[inline] - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } -} - -impl ConstantTimeGreater for Limb { - #[inline] - fn ct_gt(&self, other: &Self) -> Choice { - self.0.ct_gt(&other.0) - } -} - -impl ConstantTimeLess for Limb { - #[inline] - fn ct_lt(&self, other: &Self) -> Choice { - self.0.ct_lt(&other.0) - } -} - -impl Eq for Limb {} - -impl Ord for Limb { - fn cmp(&self, other: &Self) -> Ordering { - let mut n = 0i8; - n -= self.ct_lt(other).unwrap_u8() as i8; - n += self.ct_gt(other).unwrap_u8() as i8; - - match n { - -1 => Ordering::Less, - 1 => Ordering::Greater, - _ => { - debug_assert_eq!(n, 0); - debug_assert!(bool::from(self.ct_eq(other))); - Ordering::Equal - } - } - } -} - -impl PartialOrd for Limb { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for Limb { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -#[cfg(test)] -mod tests { - use crate::Limb; - use core::cmp::Ordering; - use subtle::{ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; - - #[test] - fn is_zero() { - assert!(bool::from(Limb::ZERO.is_zero())); - assert!(!bool::from(Limb::ONE.is_zero())); - assert!(!bool::from(Limb::MAX.is_zero())); - } - - #[test] - fn is_odd() { - assert!(!bool::from(Limb::ZERO.is_odd())); - assert!(bool::from(Limb::ONE.is_odd())); - assert!(bool::from(Limb::MAX.is_odd())); - } - - #[test] - fn ct_eq() { - let a = Limb::ZERO; - let b = Limb::MAX; - - assert!(bool::from(a.ct_eq(&a))); - assert!(!bool::from(a.ct_eq(&b))); - assert!(!bool::from(b.ct_eq(&a))); - assert!(bool::from(b.ct_eq(&b))); - } - - #[test] - fn ct_gt() { - let a = Limb::ZERO; - let b = Limb::ONE; - let c = Limb::MAX; - - assert!(bool::from(b.ct_gt(&a))); - assert!(bool::from(c.ct_gt(&a))); - assert!(bool::from(c.ct_gt(&b))); - - assert!(!bool::from(a.ct_gt(&a))); - assert!(!bool::from(b.ct_gt(&b))); - assert!(!bool::from(c.ct_gt(&c))); - - assert!(!bool::from(a.ct_gt(&b))); - assert!(!bool::from(a.ct_gt(&c))); - assert!(!bool::from(b.ct_gt(&c))); - } - - #[test] - fn ct_lt() { - let a = Limb::ZERO; - let b = Limb::ONE; - let c = Limb::MAX; - - assert!(bool::from(a.ct_lt(&b))); - assert!(bool::from(a.ct_lt(&c))); - assert!(bool::from(b.ct_lt(&c))); - - assert!(!bool::from(a.ct_lt(&a))); - assert!(!bool::from(b.ct_lt(&b))); - assert!(!bool::from(c.ct_lt(&c))); - - assert!(!bool::from(b.ct_lt(&a))); - assert!(!bool::from(c.ct_lt(&a))); - assert!(!bool::from(c.ct_lt(&b))); - } - - #[test] - fn cmp() { - assert_eq!(Limb::ZERO.cmp(&Limb::ONE), Ordering::Less); - assert_eq!(Limb::ONE.cmp(&Limb::ONE), Ordering::Equal); - assert_eq!(Limb::MAX.cmp(&Limb::ONE), Ordering::Greater); - } -} diff --git a/crypto-bigint/src/limb/encoding.rs b/crypto-bigint/src/limb/encoding.rs deleted file mode 100644 index adfe064f..00000000 --- a/crypto-bigint/src/limb/encoding.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Limb encoding - -use super::{Inner, Limb}; -use crate::Encoding; - -impl Encoding for Limb { - const BIT_SIZE: usize = super::BIT_SIZE; - const BYTE_SIZE: usize = super::BYTE_SIZE; - - #[cfg(target_pointer_width = "32")] - type Repr = [u8; 4]; - #[cfg(target_pointer_width = "64")] - type Repr = [u8; 8]; - - #[inline] - fn from_be_bytes(bytes: Self::Repr) -> Self { - Limb(Inner::from_be_bytes(bytes)) - } - - #[inline] - fn from_le_bytes(bytes: Self::Repr) -> Self { - Limb(Inner::from_le_bytes(bytes)) - } - - #[inline] - fn to_be_bytes(&self) -> Self::Repr { - self.0.to_be_bytes() - } - - #[inline] - fn to_le_bytes(&self) -> Self::Repr { - self.0.to_le_bytes() - } -} diff --git a/crypto-bigint/src/limb/from.rs b/crypto-bigint/src/limb/from.rs deleted file mode 100644 index a655b147..00000000 --- a/crypto-bigint/src/limb/from.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! `From`-like conversions for [`Limb`]. - -use super::{Inner, Limb, Wide}; - -impl Limb { - /// Create a [`Limb`] from a `u8` integer (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - pub const fn from_u8(n: u8) -> Self { - Limb(n as Inner) - } - - /// Create a [`Limb`] from a `u16` integer (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - pub const fn from_u16(n: u16) -> Self { - Limb(n as Inner) - } - - /// Create a [`Limb`] from a `u32` integer (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - pub const fn from_u32(n: u32) -> Self { - Limb(n as Inner) - } - - /// Create a [`Limb`] from a `u64` integer (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - #[cfg(target_pointer_width = "64")] - #[cfg_attr(docsrs, doc(cfg(target_pointer_width = "64")))] - pub const fn from_u64(n: u64) -> Self { - Limb(n) - } -} - -impl From for Limb { - #[inline] - fn from(n: u8) -> Limb { - Limb(n.into()) - } -} - -impl From for Limb { - #[inline] - fn from(n: u16) -> Limb { - Limb(n.into()) - } -} - -impl From for Limb { - #[inline] - fn from(n: u32) -> Limb { - Limb(n.into()) - } -} - -#[cfg(target_pointer_width = "64")] -#[cfg_attr(docsrs, doc(cfg(target_pointer_width = "64")))] -impl From for Limb { - #[inline] - fn from(n: u64) -> Limb { - Limb(n) - } -} - -impl From for Inner { - #[inline] - fn from(limb: Limb) -> Inner { - limb.0 - } -} - -impl From for Wide { - #[inline] - fn from(limb: Limb) -> Wide { - limb.0.into() - } -} diff --git a/crypto-bigint/src/limb/mul.rs b/crypto-bigint/src/limb/mul.rs deleted file mode 100644 index b31601ef..00000000 --- a/crypto-bigint/src/limb/mul.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! Limb multiplication - -use super::{Inner, Limb, Wide}; -use crate::{Encoding, Wrapping}; -use core::ops::{Mul, MulAssign}; -use subtle::CtOption; - -impl Limb { - /// Computes `self + (b * c) + carry`, returning the result along with the new carry. - #[inline(always)] - pub const fn mac(self, b: Limb, c: Limb, carry: Limb) -> (Limb, Limb) { - let a = self.0 as Wide; - let b = b.0 as Wide; - let c = c.0 as Wide; - let carry = carry.0 as Wide; - let ret = a + (b * c) + carry; - (Limb(ret as Inner), Limb((ret >> Self::BIT_SIZE) as Inner)) - } - - /// Perform wrapping multiplication, discarding overflow. - #[inline(always)] - pub const fn wrapping_mul(&self, rhs: Self) -> Self { - Limb(self.0.wrapping_mul(rhs.0)) - } - - /// Perform checked multiplication, returning a [`CtOption`] which `is_some` - /// only if the operation did not overflow. - #[inline] - pub fn checked_mul(&self, rhs: Self) -> CtOption { - let result = self.mul_wide(rhs); - let overflow = Limb((result >> Self::BIT_SIZE) as Inner); - CtOption::new(Limb(result as Inner), overflow.is_zero()) - } - - /// Compute "wide" multiplication, with a product twice the size of the input. - pub(crate) const fn mul_wide(&self, rhs: Self) -> Wide { - (self.0 as Wide) * (rhs.0 as Wide) - } -} - -impl Mul for Wrapping { - type Output = Self; - - fn mul(self, rhs: Self) -> Wrapping { - Wrapping(self.0.wrapping_mul(rhs.0)) - } -} - -impl Mul<&Wrapping> for Wrapping { - type Output = Wrapping; - - fn mul(self, rhs: &Wrapping) -> Wrapping { - Wrapping(self.0.wrapping_mul(rhs.0)) - } -} - -impl Mul> for &Wrapping { - type Output = Wrapping; - - fn mul(self, rhs: Wrapping) -> Wrapping { - Wrapping(self.0.wrapping_mul(rhs.0)) - } -} - -impl Mul<&Wrapping> for &Wrapping { - type Output = Wrapping; - - fn mul(self, rhs: &Wrapping) -> Wrapping { - Wrapping(self.0.wrapping_mul(rhs.0)) - } -} - -impl MulAssign for Wrapping { - fn mul_assign(&mut self, other: Self) { - *self = *self * other; - } -} - -impl MulAssign<&Wrapping> for Wrapping { - fn mul_assign(&mut self, other: &Self) { - *self = *self * other; - } -} - -#[cfg(test)] -mod tests { - use super::{Limb, Wide}; - - #[test] - fn mul_wide_zero_and_one() { - assert_eq!(Limb::ZERO.mul_wide(Limb::ZERO), 0); - assert_eq!(Limb::ZERO.mul_wide(Limb::ONE), 0); - assert_eq!(Limb::ONE.mul_wide(Limb::ZERO), 0); - assert_eq!(Limb::ONE.mul_wide(Limb::ONE), 1); - } - - // TODO(tarcieri): add proptests for multiplication - #[test] - fn mul_wide() { - let primes: &[u32] = &[3, 5, 17, 256, 65537]; - - for &a_int in primes { - for &b_int in primes { - let actual = Limb::from_u32(a_int).mul_wide(Limb::from_u32(b_int)); - let expected = a_int as Wide * b_int as Wide; - assert_eq!(actual, expected); - } - } - } - - #[test] - #[cfg(target_pointer_width = "32")] - fn checked_mul_ok() { - let n = Limb::from_u16(0xffff); - assert_eq!(n.checked_mul(n).unwrap(), Limb::from_u32(0xfffe_0001)); - } - - #[test] - #[cfg(target_pointer_width = "64")] - fn checked_mul_ok() { - let n = Limb::from_u32(0xffff_ffff); - assert_eq!( - n.checked_mul(n).unwrap(), - Limb::from_u64(0xffff_fffe_0000_0001) - ); - } - - #[test] - fn checked_mul_overflow() { - let n = Limb::MAX; - assert!(bool::from(n.checked_mul(n).is_none())); - } -} diff --git a/crypto-bigint/src/limb/rand.rs b/crypto-bigint/src/limb/rand.rs deleted file mode 100644 index 4e43adab..00000000 --- a/crypto-bigint/src/limb/rand.rs +++ /dev/null @@ -1,20 +0,0 @@ -//! Random number generator support - -use super::Limb; -use rand_core::{CryptoRng, RngCore}; - -impl Limb { - /// Generate a random limb - #[cfg(target_pointer_width = "32")] - #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] - pub fn random(mut rng: impl CryptoRng + RngCore) -> Self { - Self(rng.next_u32()) - } - - /// Generate a random limb - #[cfg(target_pointer_width = "64")] - #[cfg_attr(docsrs, doc(cfg(feature = "rand")))] - pub fn random(mut rng: impl CryptoRng + RngCore) -> Self { - Self(rng.next_u64()) - } -} diff --git a/crypto-bigint/src/limb/sub.rs b/crypto-bigint/src/limb/sub.rs deleted file mode 100644 index 1e1aa375..00000000 --- a/crypto-bigint/src/limb/sub.rs +++ /dev/null @@ -1,119 +0,0 @@ -//! Limb subtraction - -use super::{Inner, Limb, Wide}; -use crate::{Encoding, Wrapping}; -use core::ops::{Sub, SubAssign}; -use subtle::CtOption; - -impl Limb { - /// Computes `self - (rhs + borrow)`, returning the result along with the new borrow. - #[inline(always)] - pub const fn sbb(self, rhs: Limb, borrow: Limb) -> (Limb, Limb) { - let a = self.0 as Wide; - let b = rhs.0 as Wide; - let borrow = (borrow.0 >> (Self::BIT_SIZE - 1)) as Wide; - let ret = a.wrapping_sub(b + borrow); - (Limb(ret as Inner), Limb((ret >> Self::BIT_SIZE) as Inner)) - } - - /// Perform wrapping subtraction, discarding underflow and wrapping around - /// the boundary of the type. - #[inline(always)] - pub const fn wrapping_sub(&self, rhs: Self) -> Self { - Limb(self.0.wrapping_sub(rhs.0)) - } - - /// Perform checked subtraction, returning a [`CtOption`] which `is_some` - /// only if the operation did not overflow. - #[inline] - pub fn checked_sub(&self, rhs: Self) -> CtOption { - let (result, underflow) = self.sbb(rhs, Limb::ZERO); - CtOption::new(result, underflow.is_zero()) - } -} - -impl Sub for Wrapping { - type Output = Self; - - fn sub(self, rhs: Self) -> Wrapping { - Wrapping(self.0.wrapping_sub(rhs.0)) - } -} - -impl Sub<&Wrapping> for Wrapping { - type Output = Wrapping; - - fn sub(self, rhs: &Wrapping) -> Wrapping { - Wrapping(self.0.wrapping_sub(rhs.0)) - } -} - -impl Sub> for &Wrapping { - type Output = Wrapping; - - fn sub(self, rhs: Wrapping) -> Wrapping { - Wrapping(self.0.wrapping_sub(rhs.0)) - } -} - -impl Sub<&Wrapping> for &Wrapping { - type Output = Wrapping; - - fn sub(self, rhs: &Wrapping) -> Wrapping { - Wrapping(self.0.wrapping_sub(rhs.0)) - } -} - -impl SubAssign for Wrapping { - fn sub_assign(&mut self, other: Self) { - *self = *self - other; - } -} - -impl SubAssign<&Wrapping> for Wrapping { - fn sub_assign(&mut self, other: &Self) { - *self = *self - other; - } -} - -#[cfg(test)] -mod tests { - use crate::Limb; - - #[test] - fn sbb_no_borrow() { - let (res, borrow) = Limb::ONE.sbb(Limb::ONE, Limb::ZERO); - assert_eq!(res, Limb::ZERO); - assert_eq!(borrow, Limb::ZERO); - } - - #[test] - fn sbb_with_borrow() { - let (res, borrow) = Limb::ZERO.sbb(Limb::ONE, Limb::ZERO); - - assert_eq!(res, Limb::MAX); - assert_eq!(borrow, Limb::MAX); - } - - #[test] - fn wrapping_sub_no_borrow() { - assert_eq!(Limb::ONE.wrapping_sub(Limb::ONE), Limb::ZERO); - } - - #[test] - fn wrapping_sub_with_borrow() { - assert_eq!(Limb::ZERO.wrapping_sub(Limb::ONE), Limb::MAX); - } - - #[test] - fn checked_sub_ok() { - let result = Limb::ONE.checked_sub(Limb::ONE); - assert_eq!(result.unwrap(), Limb::ZERO); - } - - #[test] - fn checked_sub_overflow() { - let result = Limb::ZERO.checked_sub(Limb::ONE); - assert!(!bool::from(result.is_some())); - } -} diff --git a/crypto-bigint/src/macros.rs b/crypto-bigint/src/macros.rs deleted file mode 100644 index 500d46db..00000000 --- a/crypto-bigint/src/macros.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Macros - -/// Constant panicking assertion. -// TODO(tarcieri): use const panic when stable. -// See: https://github.com/rust-lang/rust/issues/51999 -macro_rules! const_assert { - ($bool:expr, $msg:expr) => { - [$msg][!$bool as usize] - }; -} - -/// Calculate the number of limbs required to represent the given number of bits. -// TODO(tarcieri): replace with `const_evaluatable_checked` (e.g. a `const fn`) when stable -#[macro_export] -macro_rules! nlimbs { - ($bits:expr) => { - $bits / $crate::limb::BIT_SIZE - }; -} - -#[cfg(test)] -mod tests { - #[cfg(target_pointer_width = "32")] - #[test] - fn nlimbs_for_bits_macro() { - assert_eq!(nlimbs!(64), 2); - assert_eq!(nlimbs!(128), 4); - assert_eq!(nlimbs!(192), 6); - assert_eq!(nlimbs!(256), 8); - } - - #[cfg(target_pointer_width = "64")] - #[test] - fn nlimbs_for_bits_macro() { - assert_eq!(nlimbs!(64), 1); - assert_eq!(nlimbs!(128), 2); - assert_eq!(nlimbs!(192), 3); - assert_eq!(nlimbs!(256), 4); - } -} diff --git a/crypto-bigint/src/traits.rs b/crypto-bigint/src/traits.rs deleted file mode 100644 index 085ae1de..00000000 --- a/crypto-bigint/src/traits.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Traits provided by this crate - -use crate::Limb; - -/// Compute `self + rhs mod p`. -pub trait AddMod { - /// Output type. - type Output; - - /// Compute `self + rhs mod p`. - /// - /// Assumes `self` and `rhs` are `< p`. - fn add_mod(&self, rhs: &Rhs, p: &Self) -> Self::Output; -} - -/// Compute `self - rhs mod p`. -pub trait SubMod { - /// Output type. - type Output; - - /// Compute `self - rhs mod p`. - /// - /// Assumes `self` and `rhs` are `< p`. - fn sub_mod(&self, rhs: &Rhs, p: &Self) -> Self::Output; -} - -/// Compute `-self mod p`. -pub trait NegMod { - /// Output type. - type Output; - - /// Compute `-self mod p`. - #[must_use] - fn neg_mod(&self, p: &Self) -> Self::Output; -} - -/// Compute `self * rhs mod p`. -/// -/// Requires `p_inv = -(p^{-1} mod 2^{BITS}) mod 2^{BITS}` to be provided for efficiency. -pub trait MulMod { - /// Output type. - type Output; - - /// Compute `self * rhs mod p`. - /// - /// Requires `p_inv = -(p^{-1} mod 2^{BITS}) mod 2^{BITS}` to be provided for efficiency. - fn mul_mod(&self, rhs: &Rhs, p: &Self, p_inv: Limb) -> Self::Output; -} - -/// Concatenate two numbers into a "wide" twice-width value, using the `rhs` -/// value as the least significant value. -pub trait Concat { - /// Concatenated output: twice the width of `Self`. - type Output; - - /// Concate the two values, with `self` as most significant and `rhs` as - /// the least significant. - fn concat(&self, rhs: &Self) -> Self::Output; -} - -/// Split a number in half, returning the most significant half followed by -/// the least significant. -pub trait Split { - /// Split output: high/low components of the value. - type Output; - - /// Split this number in half, returning its high and low components - /// respectively. - fn split(&self) -> (Self::Output, Self::Output); -} - -/// Encoding support. -pub trait Encoding: Sized { - /// Size of this integer in bits. - const BIT_SIZE: usize; - - /// Size of this integer in bytes. - const BYTE_SIZE: usize; - - /// Byte array representation. - type Repr: Copy + Clone + AsRef<[u8]> + AsMut<[u8]> + Sized; - - /// Decode from big endian bytes. - fn from_be_bytes(bytes: Self::Repr) -> Self; - - /// Decode from little endian bytes. - fn from_le_bytes(bytes: Self::Repr) -> Self; - - /// Encode to big endian bytes. - fn to_be_bytes(&self) -> Self::Repr; - - /// Encode to little endian bytes. - fn to_le_bytes(&self) -> Self::Repr; -} diff --git a/crypto-bigint/src/uint.rs b/crypto-bigint/src/uint.rs deleted file mode 100644 index 51c7c08e..00000000 --- a/crypto-bigint/src/uint.rs +++ /dev/null @@ -1,232 +0,0 @@ -//! Big unsigned integers. - -#![allow( - clippy::needless_range_loop, - clippy::many_single_char_names, - clippy::derive_hash_xor_eq -)] - -#[macro_use] -mod macros; - -mod add; -mod add_mod; -mod cmp; -mod encoding; -mod from; -mod mul; -mod neg_mod; -mod shr; -mod sub; -mod sub_mod; - -#[cfg(feature = "generic-array")] -mod array; - -#[cfg(feature = "rand")] -mod rand; - -use crate::{Concat, Encoding, Limb, Split}; -use core::fmt; -use subtle::{Choice, ConditionallySelectable}; - -#[cfg(feature = "zeroize")] -use zeroize::Zeroize; - -/// Big unsigned integer. -/// -/// Generic over the given number of `LIMBS` -// TODO(tarcieri): make generic around a specified number of bits. -#[derive(Copy, Clone, Debug, Hash)] -pub struct UInt { - /// Inner limb array. Stored from least significant to most significant. - limbs: [Limb; LIMBS], -} - -impl UInt { - /// The value `0`. - pub const ZERO: Self = Self::from_u8(0); - - /// The value `1`. - pub const ONE: Self = Self::from_u8(1); - - /// Maximum value this [`UInt`] can express. - pub const MAX: Self = Self { - limbs: [Limb::MAX; LIMBS], - }; - - /// Const-friendly [`UInt`] constructor. - pub const fn new(limbs: [Limb; LIMBS]) -> Self { - Self { limbs } - } - - /// Borrow the limbs of this [`UInt`]. - // TODO(tarcieri): eventually phase this out? - pub const fn limbs(&self) -> &[Limb; LIMBS] { - &self.limbs - } - - /// Convert this [`UInt`] into its inner limbs. - // TODO(tarcieri): eventually phase this out? - pub const fn into_limbs(self) -> [Limb; LIMBS] { - self.limbs - } -} - -// TODO(tarcieri): eventually phase this out? -impl AsRef<[Limb]> for UInt { - fn as_ref(&self) -> &[Limb] { - self.limbs() - } -} - -// TODO(tarcieri): eventually phase this out? -impl AsMut<[Limb]> for UInt { - fn as_mut(&mut self) -> &mut [Limb] { - &mut self.limbs - } -} - -impl ConditionallySelectable for UInt { - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - let mut limbs = [Limb::ZERO; LIMBS]; - - for i in 0..LIMBS { - limbs[i] = Limb::conditional_select(&a.limbs[0], &b.limbs[0], choice); - } - - Self { limbs } - } -} - -impl Default for UInt { - fn default() -> Self { - Self::ZERO - } -} - -impl fmt::Display for UInt { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::UpperHex::fmt(self, f) - } -} - -impl fmt::LowerHex for UInt { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for limb in self.limbs.iter().rev() { - fmt::LowerHex::fmt(limb, f)?; - } - Ok(()) - } -} - -impl fmt::UpperHex for UInt { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for limb in self.limbs.iter().rev() { - fmt::UpperHex::fmt(limb, f)?; - } - Ok(()) - } -} - -#[cfg(feature = "zeroize")] -#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] -impl Zeroize for UInt { - fn zeroize(&mut self) { - self.limbs.zeroize(); - } -} - -// TODO(tarcieri): use `const_evaluatable_checked` when stable to make generic around bits. -impl_uint_aliases! { - (U64, 64, "64-bit"), - (U128, 128, "128-bit"), - (U192, 192, "192-bit"), - (U256, 256, "256-bit"), - (U384, 384, "384-bit"), - (U448, 448, "448-bit"), - (U512, 512, "512-bit"), - (U768, 768, "768-bit"), - (U896, 896, "896-bit"), - (U1024, 1024, "1024-bit"), - (U1536, 1536, "1536-bit"), - (U1792, 1792, "1792-bit"), - (U2048, 2048, "2048-bit"), - (U3072, 3072, "3072-bit"), - (U3584, 3584, "3584-bit"), - (U4096, 4096, "4096-bit"), - (U6144, 6144, "6144-bit"), - (U8192, 8192, "8192-bit") -} - -// TODO(tarcieri): use `const_evaluatable_checked` when stable to make generic around bits. -impl_concat! { - (U64, 64), - (U128, 128), - (U192, 192), - (U256, 256), - (U384, 384), - (U448, 448), - (U512, 512), - (U768, 768), - (U896, 896), - (U1024, 1024), - (U1536, 1536), - (U1792, 1792), - (U2048, 2048), - (U3072, 3072), - (U4096, 4096) -} - -// TODO(tarcieri): use `const_evaluatable_checked` when stable to make generic around bits. -impl_split! { - (U128, 128), - (U192, 192), - (U256, 256), - (U384, 384), - (U448, 448), - (U512, 512), - (U768, 768), - (U896, 896), - (U1024, 1024), - (U1536, 1536), - (U1792, 1792), - (U2048, 2048), - (U3072, 3072), - (U3584, 3584), - (U4096, 4096), - (U6144, 6144), - (U8192, 8192) -} - -#[cfg(test)] -mod tests { - use crate::{Concat, Split, U128, U64}; - - #[test] - #[cfg(feature = "alloc")] - fn display() { - let hex = "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"; - let n = U128::from_be_hex(hex); - - use alloc::string::ToString; - assert_eq!(hex, n.to_string()); - } - - #[test] - fn concat() { - let hi = U64::from_u64(0x0011223344556677); - let lo = U64::from_u64(0x8899aabbccddeeff); - assert_eq!( - hi.concat(&lo), - U128::from_be_hex("00112233445566778899aabbccddeeff") - ); - } - - #[test] - fn split() { - let (hi, lo) = U128::from_be_hex("00112233445566778899aabbccddeeff").split(); - assert_eq!(hi, U64::from_u64(0x0011223344556677)); - assert_eq!(lo, U64::from_u64(0x8899aabbccddeeff)); - } -} diff --git a/crypto-bigint/src/uint/add.rs b/crypto-bigint/src/uint/add.rs deleted file mode 100644 index a7bbffa4..00000000 --- a/crypto-bigint/src/uint/add.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! [`UInt`] addition operations. - -use super::UInt; -use crate::{Limb, Wrapping}; -use core::ops::{Add, AddAssign}; -use subtle::CtOption; - -impl UInt { - /// Computes `a + b + carry`, returning the result along with the new carry. - #[inline(always)] - pub const fn adc(&self, rhs: &Self, mut carry: Limb) -> (Self, Limb) { - let mut limbs = [Limb::ZERO; LIMBS]; - let mut i = 0; - - while i < LIMBS { - let (w, c) = self.limbs[i].adc(rhs.limbs[i], carry); - limbs[i] = w; - carry = c; - i += 1; - } - - (Self { limbs }, carry) - } - - /// Perform wrapping addition, discarding overflow. - pub const fn wrapping_add(&self, rhs: &Self) -> Self { - self.adc(rhs, Limb::ZERO).0 - } - - /// Perform checked addition, returning a [`CtOption`] which `is_some` only - /// if the operation did not overflow. - pub fn checked_add(&self, rhs: &Self) -> CtOption { - let (result, carry) = self.adc(rhs, Limb::ZERO); - CtOption::new(result, carry.is_zero()) - } -} - -impl Add for Wrapping> { - type Output = Self; - - fn add(self, rhs: Self) -> Wrapping> { - Wrapping(self.0.wrapping_add(&rhs.0)) - } -} - -impl Add<&Wrapping>> for Wrapping> { - type Output = Wrapping>; - - fn add(self, rhs: &Wrapping>) -> Wrapping> { - Wrapping(self.0.wrapping_add(&rhs.0)) - } -} - -impl Add>> for &Wrapping> { - type Output = Wrapping>; - - fn add(self, rhs: Wrapping>) -> Wrapping> { - Wrapping(self.0.wrapping_add(&rhs.0)) - } -} - -impl Add<&Wrapping>> for &Wrapping> { - type Output = Wrapping>; - - fn add(self, rhs: &Wrapping>) -> Wrapping> { - Wrapping(self.0.wrapping_add(&rhs.0)) - } -} - -impl AddAssign for Wrapping> { - fn add_assign(&mut self, other: Self) { - *self = *self + other; - } -} - -impl AddAssign<&Wrapping>> for Wrapping> { - fn add_assign(&mut self, other: &Self) { - *self = *self + other; - } -} - -#[cfg(test)] -mod tests { - use crate::{Limb, U128}; - - #[test] - fn adc_no_carry() { - let (res, carry) = U128::ZERO.adc(&U128::ONE, Limb::ZERO); - assert_eq!(res, U128::ONE); - assert_eq!(carry, Limb::ZERO); - } - - #[test] - fn adc_with_carry() { - let (res, carry) = U128::MAX.adc(&U128::ONE, Limb::ZERO); - assert_eq!(res, U128::ZERO); - assert_eq!(carry, Limb::ONE); - } - - #[test] - fn wrapping_add_no_carry() { - assert_eq!(U128::ZERO.wrapping_add(&U128::ONE), U128::ONE); - } - - #[test] - fn wrapping_add_with_carry() { - assert_eq!(U128::MAX.wrapping_add(&U128::ONE), U128::ZERO); - } - - #[test] - fn checked_add_ok() { - let result = U128::ZERO.checked_add(&U128::ONE); - assert_eq!(result.unwrap(), U128::ONE); - } - - #[test] - fn checked_add_overflow() { - let result = U128::MAX.checked_add(&U128::ONE); - assert!(!bool::from(result.is_some())); - } -} diff --git a/crypto-bigint/src/uint/add_mod.rs b/crypto-bigint/src/uint/add_mod.rs deleted file mode 100644 index 0cd65001..00000000 --- a/crypto-bigint/src/uint/add_mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -//! [`UInt`] addition modulus operations. - -use crate::{AddMod, Limb, UInt}; - -impl UInt { - /// Computes `self + rhs mod p` in constant time. - /// - /// Assumes `self` and `rhs` are `< p`. - pub const fn add_mod(&self, rhs: &UInt, p: &UInt) -> UInt { - let (out, _carry) = self.adc(rhs, Limb::ZERO); - - // Subtract the modulus, to ensure the result is smaller. - out.sub_mod(p, p) - } -} - -macro_rules! impl_add_mod { - ($($size:expr),+) => { - $( - impl AddMod for UInt<$size> { - type Output = Self; - - fn add_mod(&self, rhs: &Self, p: &Self) -> Self { - debug_assert!(self < p); - debug_assert!(rhs < p); - self.add_mod(rhs, p) - } - } - )+ - }; -} - -impl_add_mod!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); - -#[cfg(all(test, feature = "rand"))] -mod tests { - use crate::UInt; - - macro_rules! test_add_mod { - ($size:expr, $test_name:ident) => { - #[test] - fn $test_name() { - use crate::Limb; - use rand_core::SeedableRng; - - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); - - let moduli = [UInt::<$size>::random(&mut rng), UInt::random(&mut rng)]; - - for p in &moduli { - let base_cases = [(1u64, 0u64, 1u64), (0, 1, 1), (0, 0, 0)]; - for (a, b, c) in &base_cases { - let a: UInt<$size> = (*a).into(); - let b: UInt<$size> = (*b).into(); - let c: UInt<$size> = (*c).into(); - - assert_eq!(c, a.add_mod(&b, p)); - } - - assert_eq!(p.add_mod(&0u64.into(), p), 0u64.into()); - assert_eq!(p.add_mod(&1u64.into(), p), 1u64.into()); - - if $size > 1 { - for _i in 0..100 { - let a: UInt<$size> = Limb::random(&mut rng).into(); - let b: UInt<$size> = Limb::random(&mut rng).into(); - let (a, b) = if a < b { (b, a) } else { (a, b) }; - - let c = a.add_mod(&b, p); - assert!(c < *p, "not reduced"); - assert_eq!(c, a.wrapping_add(&b), "result incorrect"); - } - } - - for _i in 0..100 { - let a = UInt::<$size>::random_mod(&mut rng, p); - let b = UInt::<$size>::random_mod(&mut rng, p); - - let c = a.add_mod(&b, p); - assert!(c < *p, "not reduced: {} >= {} ", c, p); - - let x = a.wrapping_add(&b); - if x < *p { - assert_eq!(c, x, "incorrect result"); - } - } - } - } - }; - } - - // Test requires 1-limb is capable of representing a 64-bit integer - #[cfg(target_pointer_width = "64")] - test_add_mod!(1, test_add1); - - test_add_mod!(2, test_add2); - test_add_mod!(3, test_add3); - test_add_mod!(4, test_add4); - test_add_mod!(5, test_add5); - test_add_mod!(6, test_add6); - test_add_mod!(7, test_add7); - test_add_mod!(8, test_add8); - test_add_mod!(9, test_add9); - test_add_mod!(10, test_add10); - test_add_mod!(11, test_add11); - test_add_mod!(12, test_add12); -} diff --git a/crypto-bigint/src/uint/array.rs b/crypto-bigint/src/uint/array.rs deleted file mode 100644 index 917ae2b0..00000000 --- a/crypto-bigint/src/uint/array.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! `generic-array` integration with `UInt`. -// TODO(tarcieri): completely phase out `generic-array` when const generics are powerful enough - -use crate::{ArrayEncoding, ByteArray}; -use generic_array::{typenum, GenericArray}; - -macro_rules! impl_uint_array_encoding { - ($(($uint:ident, $bytes:path)),+) => { - $( - #[cfg_attr(docsrs, doc(cfg(feature = "generic-array")))] - impl ArrayEncoding for super::$uint { - type ByteSize = $bytes; - - #[inline] - fn from_be_byte_array(bytes: ByteArray) -> Self { - Self::from_be_slice(&bytes) - } - - #[inline] - fn from_le_byte_array(bytes: ByteArray) -> Self { - Self::from_le_slice(&bytes) - } - - #[inline] - fn to_be_byte_array(&self) -> ByteArray { - let mut result = GenericArray::default(); - self.write_be_bytes(&mut result); - result - } - - #[inline] - fn to_le_byte_array(&self) -> ByteArray { - let mut result = GenericArray::default(); - self.write_le_bytes(&mut result); - result - } - } - )+ - }; -} - -// TODO(tarcieri): use `const_evaluatable_checked` when stable to make generic around bits. -impl_uint_array_encoding! { - (U64, typenum::U8), - (U128, typenum::U16), - (U192, typenum::U24), - (U256, typenum::U32), - (U384, typenum::U48), - (U448, typenum::U56), - (U512, typenum::U64), - (U768, typenum::U96), - (U896, typenum::U112), - (U1024, typenum::U128), - (U1536, typenum::U192), - (U1792, typenum::U224), - (U2048, typenum::U256), - (U3072, typenum::U384), - (U3584, typenum::U448), - (U4096, typenum::U512), - (U6144, typenum::U768), - (U8192, typenum::U1024) -} - -#[cfg(test)] -mod tests { - use crate::{ArrayEncoding, Limb}; - use hex_literal::hex; - - #[cfg(target_pointer_width = "32")] - use crate::U64 as UIntEx; - - #[cfg(target_pointer_width = "64")] - use crate::U128 as UIntEx; - - /// Byte array that corresponds to `UIntEx` - type ByteArray = crate::ByteArray; - - #[test] - #[cfg(target_pointer_width = "32")] - fn from_be_byte_array() { - let n = UIntEx::from_be_byte_array(hex!("0011223344556677").into()); - assert_eq!(n.limbs(), &[Limb(0x44556677), Limb(0x00112233)]); - } - - #[test] - #[cfg(target_pointer_width = "64")] - fn from_be_byte_array() { - let n = UIntEx::from_be_byte_array(hex!("00112233445566778899aabbccddeeff").into()); - assert_eq!( - n.limbs(), - &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] - ); - } - - #[test] - #[cfg(target_pointer_width = "32")] - fn from_le_byte_array() { - let n = UIntEx::from_le_byte_array(hex!("7766554433221100").into()); - assert_eq!(n.limbs(), &[Limb(0x44556677), Limb(0x00112233)]); - } - - #[test] - #[cfg(target_pointer_width = "64")] - fn from_le_byte_array() { - let n = UIntEx::from_le_byte_array(hex!("ffeeddccbbaa99887766554433221100").into()); - assert_eq!( - n.limbs(), - &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] - ); - } - - #[test] - #[cfg(target_pointer_width = "32")] - fn to_be_byte_array() { - let expected_bytes = ByteArray::from(hex!("0011223344556677")); - let actual_bytes = UIntEx::from_be_byte_array(expected_bytes).to_be_byte_array(); - assert_eq!(expected_bytes, actual_bytes); - } - - #[test] - #[cfg(target_pointer_width = "64")] - fn to_be_byte_array() { - let expected_bytes = ByteArray::from(hex!("00112233445566778899aabbccddeeff")); - let actual_bytes = UIntEx::from_be_byte_array(expected_bytes).to_be_byte_array(); - assert_eq!(expected_bytes, actual_bytes); - } - - #[test] - #[cfg(target_pointer_width = "32")] - fn to_le_byte_array() { - let expected_bytes = ByteArray::from(hex!("7766554433221100")); - let actual_bytes = UIntEx::from_le_byte_array(expected_bytes).to_le_byte_array(); - assert_eq!(expected_bytes, actual_bytes); - } - - #[test] - #[cfg(target_pointer_width = "64")] - fn to_le_byte_array() { - let expected_bytes = ByteArray::from(hex!("ffeeddccbbaa99887766554433221100")); - let actual_bytes = UIntEx::from_le_byte_array(expected_bytes).to_le_byte_array(); - assert_eq!(expected_bytes, actual_bytes); - } -} diff --git a/crypto-bigint/src/uint/cmp.rs b/crypto-bigint/src/uint/cmp.rs deleted file mode 100644 index 790078ba..00000000 --- a/crypto-bigint/src/uint/cmp.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! [`UInt`] comparisons. -//! -//! By default these are all constant-time and use the `subtle` crate. - -use super::UInt; -use crate::Limb; -use core::cmp::Ordering; -use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; - -impl UInt { - /// Determine if this [`UInt`] is equal to zero. - /// - /// # Returns - /// - /// If zero, return `Choice(1)`. Otherwise, return `Choice(0)`. - pub fn is_zero(&self) -> Choice { - self.ct_eq(&Self::ZERO) - } - - /// Is this [`UInt`] an odd number? - #[inline] - pub fn is_odd(&self) -> Choice { - self.limbs - .first() - .map(|limb| limb.is_odd()) - .unwrap_or_else(|| Choice::from(0)) - } -} - -impl ConstantTimeEq for UInt { - fn ct_eq(&self, other: &Self) -> Choice { - self.limbs - .iter() - .zip(other.limbs.iter()) - .fold(Choice::from(1), |acc, (a, b)| acc & a.ct_eq(b)) - } -} - -impl ConstantTimeGreater for UInt { - fn ct_gt(&self, other: &Self) -> Choice { - let underflow = other.sbb(self, Limb::ZERO).1; - !underflow.is_zero() - } -} - -impl ConstantTimeLess for UInt { - fn ct_lt(&self, other: &Self) -> Choice { - let underflow = self.sbb(other, Limb::ZERO).1; - !underflow.is_zero() - } -} - -impl Eq for UInt {} - -impl Ord for UInt { - fn cmp(&self, other: &Self) -> Ordering { - let mut n = 0i8; - n -= self.ct_lt(other).unwrap_u8() as i8; - n += self.ct_gt(other).unwrap_u8() as i8; - - match n { - -1 => Ordering::Less, - 1 => Ordering::Greater, - _ => { - debug_assert_eq!(n, 0); - debug_assert!(bool::from(self.ct_eq(other))); - Ordering::Equal - } - } - } -} - -impl PartialOrd for UInt { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for UInt { - fn eq(&self, other: &Self) -> bool { - self.ct_eq(other).into() - } -} - -#[cfg(test)] -mod tests { - use crate::U128; - use subtle::{ - Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess, - }; - - #[test] - fn is_zero() { - assert!(bool::from(U128::ZERO.is_zero())); - assert!(!bool::from(U128::ONE.is_zero())); - assert!(!bool::from(U128::MAX.is_zero())); - } - - #[test] - fn is_odd() { - assert!(!bool::from(U128::ZERO.is_odd())); - assert!(bool::from(U128::ONE.is_odd())); - assert!(bool::from(U128::MAX.is_odd())); - } - - #[test] - fn conditional_select() { - let a = U128::ZERO; - let b = U128::MAX; - - assert_eq!(U128::conditional_select(&a, &b, Choice::from(0)), a); - assert_eq!(U128::conditional_select(&a, &b, Choice::from(1)), b); - } - - #[test] - fn ct_eq() { - let a = U128::ZERO; - let b = U128::MAX; - - assert!(bool::from(a.ct_eq(&a))); - assert!(!bool::from(a.ct_eq(&b))); - assert!(!bool::from(b.ct_eq(&a))); - assert!(bool::from(b.ct_eq(&b))); - } - - #[test] - fn ct_gt() { - let a = U128::ZERO; - let b = U128::ONE; - let c = U128::MAX; - - assert!(bool::from(b.ct_gt(&a))); - assert!(bool::from(c.ct_gt(&a))); - assert!(bool::from(c.ct_gt(&b))); - - assert!(!bool::from(a.ct_gt(&a))); - assert!(!bool::from(b.ct_gt(&b))); - assert!(!bool::from(c.ct_gt(&c))); - - assert!(!bool::from(a.ct_gt(&b))); - assert!(!bool::from(a.ct_gt(&c))); - assert!(!bool::from(b.ct_gt(&c))); - } - - #[test] - fn ct_lt() { - let a = U128::ZERO; - let b = U128::ONE; - let c = U128::MAX; - - assert!(bool::from(a.ct_lt(&b))); - assert!(bool::from(a.ct_lt(&c))); - assert!(bool::from(b.ct_lt(&c))); - - assert!(!bool::from(a.ct_lt(&a))); - assert!(!bool::from(b.ct_lt(&b))); - assert!(!bool::from(c.ct_lt(&c))); - - assert!(!bool::from(b.ct_lt(&a))); - assert!(!bool::from(c.ct_lt(&a))); - assert!(!bool::from(c.ct_lt(&b))); - } -} diff --git a/crypto-bigint/src/uint/encoding.rs b/crypto-bigint/src/uint/encoding.rs deleted file mode 100644 index 485bed04..00000000 --- a/crypto-bigint/src/uint/encoding.rs +++ /dev/null @@ -1,304 +0,0 @@ -//! Const-friendly decoding operations for [`UInt`] - -use super::UInt; -use crate::{limb, Encoding, Limb}; - -impl UInt { - /// Create a new [`UInt`] from the provided big endian bytes. - pub const fn from_be_slice(bytes: &[u8]) -> Self { - const_assert!( - bytes.len() == limb::BYTE_SIZE * LIMBS, - "bytes are not the expected size" - ); - - let mut decoder = Decoder::new(); - let mut i = 0; - - while i < limb::BYTE_SIZE * LIMBS { - i += 1; - decoder = decoder.add_byte(bytes[bytes.len() - i]); - } - - decoder.finish() - } - - /// Create a new [`UInt`] from the provided big endian hex string. - pub const fn from_be_hex(hex: &str) -> Self { - let bytes = hex.as_bytes(); - - const_assert!( - bytes.len() == limb::BYTE_SIZE * LIMBS * 2, - "hex string is not the expected size" - ); - - let mut decoder = Decoder::new(); - let mut i = 0; - - while i < limb::BYTE_SIZE * LIMBS * 2 { - i += 2; - let offset = bytes.len() - i; - let byte = decode_hex_byte([bytes[offset], bytes[offset + 1]]); - decoder = decoder.add_byte(byte); - } - - decoder.finish() - } - - /// Create a new [`UInt`] from the provided little endian bytes. - pub const fn from_le_slice(bytes: &[u8]) -> Self { - const_assert!( - bytes.len() == limb::BYTE_SIZE * LIMBS, - "bytes are not the expected size" - ); - - let mut decoder = Decoder::new(); - let mut i = 0; - - while i < limb::BYTE_SIZE * LIMBS { - decoder = decoder.add_byte(bytes[i]); - i += 1; - } - - decoder.finish() - } - - /// Create a new [`UInt`] from the provided little endian hex string. - pub const fn from_le_hex(hex: &str) -> Self { - let bytes = hex.as_bytes(); - - const_assert!( - bytes.len() == limb::BYTE_SIZE * LIMBS * 2, - "bytes are not the expected size" - ); - - let mut decoder = Decoder::new(); - let mut i = 0; - - while i < limb::BYTE_SIZE * LIMBS * 2 { - let byte = decode_hex_byte([bytes[i], bytes[i + 1]]); - decoder = decoder.add_byte(byte); - i += 2; - } - - decoder.finish() - } - - /// Serialize this [`UInt`] as big-endian, writing it into the provided - /// byte slice. - #[inline] - #[cfg_attr(docsrs, doc(cfg(feature = "generic-array")))] - pub(crate) fn write_be_bytes(&self, out: &mut [u8]) { - debug_assert_eq!(out.len(), limb::BYTE_SIZE * LIMBS); - - for (src, dst) in self - .limbs - .iter() - .rev() - .cloned() - .zip(out.chunks_exact_mut(limb::BYTE_SIZE)) - { - dst.copy_from_slice(&src.to_be_bytes()); - } - } - - /// Serialize this [`UInt`] as little-endian, writing it into the provided - /// byte slice. - #[inline] - #[cfg_attr(docsrs, doc(cfg(feature = "generic-array")))] - pub(crate) fn write_le_bytes(&self, out: &mut [u8]) { - debug_assert_eq!(out.len(), limb::BYTE_SIZE * LIMBS); - - for (src, dst) in self - .limbs - .iter() - .cloned() - .zip(out.chunks_exact_mut(limb::BYTE_SIZE)) - { - dst.copy_from_slice(&src.to_le_bytes()); - } - } -} - -#[derive(Clone, Debug)] -struct Decoder { - /// Limbs being decoded. - /// - /// Stored from least significant to most significant. - limbs: [Limb; LIMBS], - - /// Current limb being decoded. - index: usize, - - /// Total number of bytes consumed. - bytes: usize, -} - -impl Decoder { - /// Create a new decoder. - pub const fn new() -> Self { - Self { - limbs: [Limb::ZERO; LIMBS], - index: 0, - bytes: 0, - } - } - - /// Add a byte onto the [`UInt`] being decoded. - pub const fn add_byte(mut self, byte: u8) -> Self { - if self.bytes == limb::BYTE_SIZE { - const_assert!(self.index < LIMBS, "too many bytes in UInt"); - self.index += 1; - self.bytes = 0; - } - - self.limbs[self.index].0 |= (byte as limb::Inner) << (self.bytes * 8); - self.bytes += 1; - self - } - - /// Finish decoding a [`UInt`], returning a decoded value only if we've - /// received the expected number of bytes. - pub const fn finish(self) -> UInt { - const_assert!(self.index == LIMBS - 1, "decoded UInt is missing limbs"); - const_assert!( - self.bytes == limb::BYTE_SIZE, - "decoded UInt is missing bytes" - ); - UInt { limbs: self.limbs } - } -} - -impl Default for Decoder { - fn default() -> Self { - Self::new() - } -} - -/// Decode a single byte encoded as two hexadecimal characters. -const fn decode_hex_byte(bytes: [u8; 2]) -> u8 { - let mut i = 0; - let mut result = 0u8; - - while i < 2 { - result <<= 4; - result |= match bytes[i] { - b @ b'0'..=b'9' => b - b'0', - b @ b'a'..=b'f' => 10 + b - b'a', - b @ b'A'..=b'F' => 10 + b - b'A', - b => { - const_assert!( - matches!(b, b'0'..=b'9' | b'a' ..= b'f' | b'A'..=b'F'), - "invalid hex byte" - ); - 0 - } - }; - - i += 1; - } - - result -} - -#[cfg(test)] -mod tests { - use crate::Limb; - use hex_literal::hex; - - #[cfg(feature = "alloc")] - use {crate::U128, alloc::format}; - - #[cfg(target_pointer_width = "32")] - use crate::U64 as UIntEx; - - #[cfg(target_pointer_width = "64")] - use crate::U128 as UIntEx; - - #[test] - #[cfg(target_pointer_width = "32")] - fn from_be_bytes() { - let bytes = hex!("0011223344556677"); - let n = UIntEx::from_be_slice(&bytes); - assert_eq!(n.limbs(), &[Limb(0x44556677), Limb(0x00112233)]); - } - - #[test] - #[cfg(target_pointer_width = "64")] - fn from_be_bytes() { - let bytes = hex!("00112233445566778899aabbccddeeff"); - let n = UIntEx::from_be_slice(&bytes); - assert_eq!( - n.limbs(), - &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] - ); - } - - #[test] - #[cfg(target_pointer_width = "32")] - fn from_le_bytes() { - let bytes = hex!("7766554433221100"); - let n = UIntEx::from_le_slice(&bytes); - assert_eq!(n.limbs(), &[Limb(0x44556677), Limb(0x00112233)]); - } - - #[test] - #[cfg(target_pointer_width = "64")] - fn from_le_bytes() { - let bytes = hex!("ffeeddccbbaa99887766554433221100"); - let n = UIntEx::from_le_slice(&bytes); - assert_eq!( - n.limbs(), - &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] - ); - } - - #[test] - #[cfg(target_pointer_width = "32")] - fn from_be_hex() { - let n = UIntEx::from_be_hex("0011223344556677"); - assert_eq!(n.limbs(), &[Limb(0x44556677), Limb(0x00112233)]); - } - - #[test] - #[cfg(target_pointer_width = "64")] - fn from_be_hex() { - let n = UIntEx::from_be_hex("00112233445566778899aabbccddeeff"); - assert_eq!( - n.limbs(), - &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] - ); - } - - #[test] - #[cfg(target_pointer_width = "32")] - fn from_le_hex() { - let n = UIntEx::from_le_hex("7766554433221100"); - assert_eq!(n.limbs(), &[Limb(0x44556677), Limb(0x00112233)]); - } - - #[test] - #[cfg(target_pointer_width = "64")] - fn from_le_hex() { - let n = UIntEx::from_le_hex("ffeeddccbbaa99887766554433221100"); - assert_eq!( - n.limbs(), - &[Limb(0x8899aabbccddeeff), Limb(0x0011223344556677)] - ); - } - - #[cfg(feature = "alloc")] - #[test] - fn hex_upper() { - let hex = "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"; - let n = U128::from_be_hex(hex); - assert_eq!(hex, format!("{:X}", n)); - } - - #[cfg(feature = "alloc")] - #[test] - fn hex_lower() { - let hex = "aaaaaaaabbbbbbbbccccccccdddddddd"; - let n = U128::from_be_hex(hex); - assert_eq!(hex, format!("{:x}", n)); - } -} diff --git a/crypto-bigint/src/uint/from.rs b/crypto-bigint/src/uint/from.rs deleted file mode 100644 index 54e841bb..00000000 --- a/crypto-bigint/src/uint/from.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! `From`-like conversions for [`UInt`]. - -use crate::{limb, Limb, Split, UInt, U128, U64}; - -impl UInt { - /// Create a [`UInt`] from a `u8` (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - pub const fn from_u8(n: u8) -> Self { - const_assert!(LIMBS >= 1, "number of limbs must be greater than zero"); - let mut limbs = [Limb::ZERO; LIMBS]; - limbs[0].0 = n as limb::Inner; - Self { limbs } - } - - /// Create a [`UInt`] from a `u16` (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - pub const fn from_u16(n: u16) -> Self { - const_assert!(LIMBS >= 1, "number of limbs must be greater than zero"); - let mut limbs = [Limb::ZERO; LIMBS]; - limbs[0].0 = n as limb::Inner; - Self { limbs } - } - - /// Create a [`UInt`] from a `u32` (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - pub const fn from_u32(n: u32) -> Self { - const_assert!(LIMBS >= 1, "number of limbs must be greater than zero"); - let mut limbs = [Limb::ZERO; LIMBS]; - limbs[0].0 = n as limb::Inner; - Self { limbs } - } - - /// Create a [`UInt`] from a `u64` (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - #[cfg(target_pointer_width = "32")] - pub const fn from_u64(n: u64) -> Self { - const_assert!(LIMBS >= 2, "number of limbs must be two or greater"); - let mut limbs = [Limb::ZERO; LIMBS]; - limbs[0].0 = (n & 0xFFFFFFFF) as u32; - limbs[1].0 = (n >> 32) as u32; - Self { limbs } - } - - /// Create a [`UInt`] from a `u64` (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - #[cfg(target_pointer_width = "64")] - pub const fn from_u64(n: u64) -> Self { - const_assert!(LIMBS >= 1, "number of limbs must be greater than zero"); - let mut limbs = [Limb::ZERO; LIMBS]; - limbs[0].0 = n as limb::Inner; - Self { limbs } - } - - /// Create a [`UInt`] from a `u128` (const-friendly) - // TODO(tarcieri): replace with `const impl From` when stable - pub const fn from_u128(n: u128) -> Self { - const_assert!( - LIMBS >= (128 / limb::BIT_SIZE), - "number of limbs must be greater than zero" - ); - - let lo = U64::from_u64((n & 0xffff_ffff_ffff_ffff) as u64); - let hi = U64::from_u64((n >> 64) as u64); - - let mut limbs = [Limb::ZERO; LIMBS]; - - let mut i = 0; - while i < lo.limbs.len() { - limbs[i] = lo.limbs[i]; - i += 1; - } - - let mut j = 0; - while j < hi.limbs.len() { - limbs[i + j] = hi.limbs[j]; - j += 1; - } - - Self { limbs } - } - - /// Create a [`UInt`] from an array of the [`limb::Inner`] - /// unsigned integer type. - // TODO(tarcieri): replace with `const impl From<[limb::Inner; LIMBS]>` when stable - #[inline] - pub const fn from_uint_array(arr: [limb::Inner; LIMBS]) -> Self { - let mut limbs = [Limb::ZERO; LIMBS]; - let mut i = 0; - - while i < LIMBS { - limbs[i] = Limb(arr[i]); - i += 1; - } - - Self { limbs } - } - - /// Create an array of [`limb::Inner`] unsigned integer type from a [`UInt`]. - #[inline] - // TODO(tarcieri): replace with `const impl From for [limb::Inner; LIMBS]` when stable - pub const fn to_uint_array(self) -> [limb::Inner; LIMBS] { - let mut arr = [0; LIMBS]; - let mut i = 0; - - while i < LIMBS { - arr[i] = self.limbs[i].0; - i += 1; - } - - arr - } -} - -impl From for UInt { - fn from(n: u8) -> Self { - // TODO(tarcieri): const where clause when possible - debug_assert!(LIMBS > 0, "limbs must be non-zero"); - Self::from_u8(n) - } -} - -impl From for UInt { - fn from(n: u16) -> Self { - // TODO(tarcieri): const where clause when possible - debug_assert!(LIMBS > 0, "limbs must be non-zero"); - Self::from_u16(n) - } -} - -impl From for UInt { - fn from(n: u32) -> Self { - // TODO(tarcieri): const where clause when possible - debug_assert!(LIMBS > 0, "limbs must be non-zero"); - Self::from_u32(n) - } -} - -impl From for UInt { - fn from(n: u64) -> Self { - // TODO(tarcieri): const where clause when possible - debug_assert!(LIMBS >= (64 / limb::BIT_SIZE), "not enough limbs"); - Self::from_u64(n) - } -} - -impl From for UInt { - fn from(n: u128) -> Self { - // TODO(tarcieri): const where clause when possible - debug_assert!(LIMBS >= (128 / limb::BIT_SIZE), "not enough limbs"); - Self::from_u128(n) - } -} - -#[cfg(target_pointer_width = "32")] -#[cfg_attr(docsrs, doc(cfg(target_pointer_width = "32")))] -impl From for u64 { - fn from(n: U64) -> u64 { - (n.limbs[0].0 as u64) | ((n.limbs[1].0 as u64) << 32) - } -} - -#[cfg(target_pointer_width = "64")] -#[cfg_attr(docsrs, doc(cfg(target_pointer_width = "64")))] -impl From for u64 { - fn from(n: U64) -> u64 { - n.limbs[0].into() - } -} - -impl From for u128 { - fn from(n: U128) -> u128 { - let (hi, lo) = n.split(); - (u64::from(hi) as u128) << 64 | (u64::from(lo) as u128) - } -} - -impl From<[limb::Inner; LIMBS]> for UInt { - fn from(arr: [limb::Inner; LIMBS]) -> Self { - Self::from_uint_array(arr) - } -} - -impl From> for [limb::Inner; LIMBS] { - fn from(n: UInt) -> [limb::Inner; LIMBS] { - n.to_uint_array() - } -} - -impl From<[Limb; LIMBS]> for UInt { - fn from(limbs: [Limb; LIMBS]) -> Self { - Self { limbs } - } -} - -impl From> for [Limb; LIMBS] { - fn from(n: UInt) -> [Limb; LIMBS] { - n.limbs - } -} - -impl From for UInt { - fn from(limb: Limb) -> Self { - limb.0.into() - } -} - -#[cfg(test)] -mod tests { - use crate::{limb, Limb, U128}; - - #[cfg(target_pointer_width = "32")] - use crate::U64 as UIntEx; - - #[cfg(target_pointer_width = "64")] - use crate::U128 as UIntEx; - - #[test] - fn from_u8() { - let n = UIntEx::from(42u8); - assert_eq!(n.limbs(), &[Limb(42), Limb(0)]); - } - - #[test] - fn from_u16() { - let n = UIntEx::from(42u16); - assert_eq!(n.limbs(), &[Limb(42), Limb(0)]); - } - - #[test] - fn from_u64() { - let n = UIntEx::from(42u64); - assert_eq!(n.limbs(), &[Limb(42), Limb(0)]); - } - - #[test] - fn from_u128() { - let n = U128::from(42u128); - assert_eq!(&n.limbs()[..2], &[Limb(42), Limb(0)]); - assert_eq!(u128::from(n), 42u128); - } - - #[test] - fn array_round_trip() { - let arr1 = [1, 2]; - let n = UIntEx::from(arr1); - let arr2: [limb::Inner; 2] = n.into(); - assert_eq!(arr1, arr2); - } -} diff --git a/crypto-bigint/src/uint/macros.rs b/crypto-bigint/src/uint/macros.rs deleted file mode 100644 index 5ff76688..00000000 --- a/crypto-bigint/src/uint/macros.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Macros for defining aliases and relationships between `UInt` types. -// TODO(tarcieri): replace these with `const_evaluatable_checked` exprs when stable - -// TODO(tarcieri): use `const_evaluatable_checked` when stable to make generic around bits. -macro_rules! impl_uint_aliases { - ($(($name:ident, $bits:expr, $doc:expr)),+) => { - $( - #[doc = $doc] - #[doc="unsigned big integer"] - pub type $name = UInt<{nlimbs!($bits)}>; - - impl Encoding for $name { - const BIT_SIZE: usize = $bits; - const BYTE_SIZE: usize = $bits / 8; - - type Repr = [u8; $bits / 8]; - - fn from_be_bytes(bytes: Self::Repr) -> Self { - Self::from_be_slice(&bytes) - } - - fn from_le_bytes(bytes: Self::Repr) -> Self { - Self::from_be_slice(&bytes) - } - - #[inline] - fn to_be_bytes(&self) -> Self::Repr { - let mut result = [0u8; $bits / 8]; - self.write_be_bytes(&mut result); - result - } - - #[inline] - fn to_le_bytes(&self) -> Self::Repr { - let mut result = [0u8; $bits / 8]; - self.write_le_bytes(&mut result); - result - } - } - )+ - }; -} - -// TODO(tarcieri): use `const_evaluatable_checked` when stable to make generic around bits. -macro_rules! impl_concat { - ($(($name:ident, $bits:expr)),+) => { - $( - impl Concat for $name { - type Output = UInt<{nlimbs!($bits) * 2}>; - - fn concat(&self, rhs: &Self) -> Self::Output { - let mut output = Self::Output::default(); - let (lo, hi) = output.limbs.split_at_mut(self.limbs.len()); - lo.copy_from_slice(&rhs.limbs); - hi.copy_from_slice(&self.limbs); - output - } - } - - impl From<($name, $name)> for UInt<{nlimbs!($bits) * 2}> { - fn from(nums: ($name, $name)) -> UInt<{nlimbs!($bits) * 2}> { - nums.0.concat(&nums.1) - } - } - )+ - }; -} - -// TODO(tarcieri): use `const_evaluatable_checked` when stable to make generic around bits. -macro_rules! impl_split { - ($(($name:ident, $bits:expr)),+) => { - $( - impl Split for $name { - type Output = UInt<{nlimbs!($bits) / 2}>; - - fn split(&self) -> (Self::Output, Self::Output) { - let mut hi_out = Self::Output::default(); - let mut lo_out = Self::Output::default(); - let (lo_in, hi_in) = self.limbs.split_at(self.limbs.len() / 2); - hi_out.limbs.copy_from_slice(&hi_in); - lo_out.limbs.copy_from_slice(&lo_in); - (hi_out, lo_out) - } - } - - impl From<$name> for (UInt<{nlimbs!($bits) / 2}>, UInt<{nlimbs!($bits) / 2}>) { - fn from(num: $name) -> (UInt<{nlimbs!($bits) / 2}>, UInt<{nlimbs!($bits) / 2}>) { - num.split() - } - } - )+ - }; -} diff --git a/crypto-bigint/src/uint/mul.rs b/crypto-bigint/src/uint/mul.rs deleted file mode 100644 index 7113bdf0..00000000 --- a/crypto-bigint/src/uint/mul.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! [`UInt`] addition operations. - -use super::UInt; -use crate::{Concat, Limb, Wrapping}; -use core::ops::{Mul, MulAssign}; -use subtle::CtOption; - -impl UInt { - /// Compute "wide" multiplication, with a product twice the size of the input. - // TODO(tarcieri): use `concat` (or similar) when const trait is stable - pub const fn mul_wide(&self, rhs: &Self) -> (Self, Self) { - let mut i = 0; - let mut lo = Self::ZERO; - let mut hi = Self::ZERO; - - // Schoolbook multiplication. - // TODO(tarcieri): use Karatsuba for better performance? - while i < LIMBS { - let mut j = 0; - let mut carry = Limb::ZERO; - - while j < LIMBS { - let k = i + j; - - if k >= LIMBS { - let (n, c) = hi.limbs[k - LIMBS].mac(self.limbs[i], rhs.limbs[j], carry); - hi.limbs[k - LIMBS] = n; - carry = c; - } else { - let (n, c) = lo.limbs[k].mac(self.limbs[i], rhs.limbs[j], carry); - lo.limbs[k] = n; - carry = c; - } - - j += 1; - } - - hi.limbs[i + j - LIMBS] = carry; - i += 1; - } - - (hi, lo) - } - - /// Perform wrapping multiplication, discarding overflow. - pub const fn wrapping_mul(&self, rhs: &Self) -> Self { - self.mul_wide(rhs).1 - } - - /// Perform checked multiplication, returning a [`CtOption`] which `is_some` - /// only if the operation did not overflow. - pub fn checked_mul(&self, rhs: &Self) -> CtOption { - let (hi, lo) = self.mul_wide(rhs); - CtOption::new(lo, hi.is_zero()) - } - - /// Square self, returning a "wide" result. - pub fn square(&self) -> ::Output - where - Self: Concat, - { - let (hi, lo) = self.mul_wide(self); - hi.concat(&lo) - } -} - -impl Mul for Wrapping> { - type Output = Self; - - fn mul(self, rhs: Self) -> Wrapping> { - Wrapping(self.0.wrapping_mul(&rhs.0)) - } -} - -impl Mul<&Wrapping>> for Wrapping> { - type Output = Wrapping>; - - fn mul(self, rhs: &Wrapping>) -> Wrapping> { - Wrapping(self.0.wrapping_mul(&rhs.0)) - } -} - -impl Mul>> for &Wrapping> { - type Output = Wrapping>; - - fn mul(self, rhs: Wrapping>) -> Wrapping> { - Wrapping(self.0.wrapping_mul(&rhs.0)) - } -} - -impl Mul<&Wrapping>> for &Wrapping> { - type Output = Wrapping>; - - fn mul(self, rhs: &Wrapping>) -> Wrapping> { - Wrapping(self.0.wrapping_mul(&rhs.0)) - } -} - -impl MulAssign for Wrapping> { - fn mul_assign(&mut self, other: Self) { - *self = *self * other; - } -} - -impl MulAssign<&Wrapping>> for Wrapping> { - fn mul_assign(&mut self, other: &Self) { - *self = *self * other; - } -} - -#[cfg(test)] -mod tests { - use crate::Split; - use crate::U64; - - #[test] - fn mul_wide_zero_and_one() { - assert_eq!(U64::ZERO.mul_wide(&U64::ZERO), (U64::ZERO, U64::ZERO)); - assert_eq!(U64::ZERO.mul_wide(&U64::ONE), (U64::ZERO, U64::ZERO)); - assert_eq!(U64::ONE.mul_wide(&U64::ZERO), (U64::ZERO, U64::ZERO)); - assert_eq!(U64::ONE.mul_wide(&U64::ONE), (U64::ZERO, U64::ONE)); - } - - // TODO(tarcieri): add proptests for multiplication - #[test] - fn mul_wide_lo_only() { - let primes: &[u32] = &[3, 5, 17, 256, 65537]; - - for &a_int in primes { - for &b_int in primes { - let (hi, lo) = U64::from_u32(a_int).mul_wide(&U64::from_u32(b_int)); - let expected = U64::from_u64(a_int as u64 * b_int as u64); - assert_eq!(lo, expected); - assert!(bool::from(hi.is_zero())); - } - } - } - - #[test] - fn checked_mul_ok() { - let n = U64::from_u32(0xffff_ffff); - assert_eq!( - n.checked_mul(&n).unwrap(), - U64::from_u64(0xffff_fffe_0000_0001) - ); - } - - #[test] - fn checked_mul_overflow() { - let n = U64::from_u64(0xffff_ffff_ffff_ffff); - assert!(bool::from(n.checked_mul(&n).is_none())); - } - - #[test] - fn square() { - let n = U64::from_u64(0xffff_ffff_ffff_ffff); - let (hi, lo) = n.square().split(); - assert_eq!(lo, U64::from_u64(1)); - assert_eq!(hi, U64::from_u64(0xffff_ffff_ffff_fffe)); - } -} diff --git a/crypto-bigint/src/uint/neg_mod.rs b/crypto-bigint/src/uint/neg_mod.rs deleted file mode 100644 index f0709bb1..00000000 --- a/crypto-bigint/src/uint/neg_mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! [`UInt`] subtraction modulus operations. - -use crate::{Limb, NegMod, UInt}; - -impl UInt { - /// Computes `-a mod p` in constant time. - pub const fn neg_mod(&self, p: &Self) -> Self { - let mut tmp = [Limb::ZERO; LIMBS]; - - // Subtract `a` from `p` to negate. Ignore the final - // borrow because it cannot underflow; a is guaranteed to - // be in the field. - let mut borrow = Limb::ZERO; - let mut i = 0; - - while i < LIMBS { - let (l, b) = p.limbs[i].sbb(self.limbs[i], borrow); - tmp[i] = l; - borrow = b; - - i += 1; - } - - // `tmp` could be `p` if `a` was zero. Create a mask that is - // zero if `a` was zero, and `Limb::MAX` if self was nonzero. - // FIXME: constant time comparison - let mut self_or = self.limbs[0]; - let mut i = 1; - - while i < LIMBS { - self_or = self_or.bitor(self.limbs[i]); - i += 1; - } - - let v = if self_or.eq_vartime(&Limb::ZERO) { - Limb::ONE - } else { - Limb::ZERO - }; - - let mask = v.wrapping_sub(Limb::ONE); - - let mut i = 0; - - while i < LIMBS { - tmp[i] = tmp[i].bitand(mask); - i += 1; - } - - UInt::new(tmp) - } -} - -macro_rules! impl_neg_mod { - ($($size:expr),+) => { - $( - impl NegMod for UInt<$size> { - type Output = Self; - - fn neg_mod(&self, p: &Self) -> Self { - debug_assert!(self < p); - self.neg_mod(p) - } - } - )+ - }; -} - -impl_neg_mod!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); diff --git a/crypto-bigint/src/uint/rand.rs b/crypto-bigint/src/uint/rand.rs deleted file mode 100644 index 683dd652..00000000 --- a/crypto-bigint/src/uint/rand.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Random number generator support - -use super::UInt; -use crate::Limb; -use rand_core::{CryptoRng, RngCore}; -use subtle::ConstantTimeLess; - -#[cfg_attr(docsrs, doc(cfg(feature = "rand")))] -impl UInt { - /// Generate a cryptographically secure random [`UInt`]. - pub fn random(mut rng: impl CryptoRng + RngCore) -> Self { - let mut limbs = [Limb::default(); LIMBS]; - - for limb in &mut limbs { - *limb = Limb::random(&mut rng) - } - - limbs.into() - } - - /// Generate a cryptographically secure random [`UInt`] which is less than - /// a given `modulus`. - /// - /// This function uses rejection sampling, a method which produces an - /// unbiased distribution of in-range values provided the underlying - /// [`CryptoRng`] is unbiased, but runs in variable-time. - /// - /// The variable-time nature of the algorithm should not pose a security - /// issue so long as the underlying random number generator is truly a - /// [`CryptoRng`], where previous outputs are unrelated to subsequent - /// outputs and do not reveal information about the RNG's internal state. - pub fn random_mod(mut rng: impl CryptoRng + RngCore, modulus: &Self) -> Self { - loop { - let n = Self::random(&mut rng); - - if n.ct_lt(modulus).into() { - return n; - } - } - } -} diff --git a/crypto-bigint/src/uint/shr.rs b/crypto-bigint/src/uint/shr.rs deleted file mode 100644 index 2e552f0b..00000000 --- a/crypto-bigint/src/uint/shr.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! [`UInt`] bitwise right shift operations. - -use super::UInt; -use crate::{limb, Limb}; -use core::ops::Shr; - -impl UInt { - /// Computes `self >> n`. - /// - /// NOTE: this operation is variable time with respect to `n` *ONLY*. - /// - /// When used with a fixed `n`, this function is constant-time with respect - /// to `self`. - #[inline(always)] - pub const fn shr_vartime(&self, shift: usize) -> Self { - let full_shifts = shift / limb::BIT_SIZE; - let small_shift = shift & (limb::BIT_SIZE - 1); - let mut limbs = [Limb::ZERO; LIMBS]; - - if shift > limb::BIT_SIZE * LIMBS { - return Self { limbs }; - } - - let n = LIMBS - full_shifts; - let mut i = 0; - - if small_shift == 0 { - while i < n { - limbs[i] = Limb(self.limbs[i + full_shifts].0); - i += 1; - } - } else { - while i < n { - let mut lo = self.limbs[i + full_shifts].0 >> small_shift; - - if i < (LIMBS - 1) - full_shifts { - lo |= self.limbs[i + full_shifts + 1].0 << (limb::BIT_SIZE - small_shift); - } - - limbs[i] = Limb(lo); - i += 1; - } - } - - Self { limbs } - } -} - -impl Shr for UInt { - type Output = UInt; - - /// NOTE: this operation is variable time with respect to `rhs` *ONLY*. - /// - /// When used with a fixed `rhs`, this function is constant-time with respect - /// to `self`. - fn shr(self, rhs: usize) -> UInt { - self.shr_vartime(rhs) - } -} - -impl Shr for &UInt { - type Output = UInt; - - /// NOTE: this operation is variable time with respect to `rhs` *ONLY*. - /// - /// When used with a fixed `rhs`, this function is constant-time with respect - /// to `self`. - fn shr(self, rhs: usize) -> UInt { - self.shr_vartime(rhs) - } -} - -#[cfg(test)] -mod tests { - use crate::U256; - - const N: U256 = - U256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"); - - const N_2: U256 = - U256::from_be_hex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0"); - - #[test] - fn shr1() { - assert_eq!(N >> 1, N_2); - } -} diff --git a/crypto-bigint/src/uint/sub.rs b/crypto-bigint/src/uint/sub.rs deleted file mode 100644 index 3e68e222..00000000 --- a/crypto-bigint/src/uint/sub.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! [`UInt`] addition operations. - -use super::UInt; -use crate::{Limb, Wrapping}; -use core::ops::{Sub, SubAssign}; -use subtle::CtOption; - -impl UInt { - /// Computes `a - (b + borrow)`, returning the result along with the new borrow. - #[inline(always)] - pub const fn sbb(&self, rhs: &Self, mut borrow: Limb) -> (Self, Limb) { - let mut limbs = [Limb::ZERO; LIMBS]; - let mut i = 0; - - while i < LIMBS { - let (w, b) = self.limbs[i].sbb(rhs.limbs[i], borrow); - limbs[i] = w; - borrow = b; - i += 1; - } - - (Self { limbs }, borrow) - } - - /// Perform wrapping subtraction, discarding underflow and wrapping around - /// the boundary of the type. - pub const fn wrapping_sub(&self, rhs: &Self) -> Self { - self.sbb(rhs, Limb::ZERO).0 - } - - /// Perform checked subtraction, returning a [`CtOption`] which `is_some` - /// only if the operation did not overflow. - pub fn checked_sub(&self, rhs: &Self) -> CtOption { - let (result, underflow) = self.sbb(rhs, Limb::ZERO); - CtOption::new(result, underflow.is_zero()) - } -} - -impl Sub for Wrapping> { - type Output = Self; - - fn sub(self, rhs: Self) -> Wrapping> { - Wrapping(self.0.wrapping_sub(&rhs.0)) - } -} - -impl Sub<&Wrapping>> for Wrapping> { - type Output = Wrapping>; - - fn sub(self, rhs: &Wrapping>) -> Wrapping> { - Wrapping(self.0.wrapping_sub(&rhs.0)) - } -} - -impl Sub>> for &Wrapping> { - type Output = Wrapping>; - - fn sub(self, rhs: Wrapping>) -> Wrapping> { - Wrapping(self.0.wrapping_sub(&rhs.0)) - } -} - -impl Sub<&Wrapping>> for &Wrapping> { - type Output = Wrapping>; - - fn sub(self, rhs: &Wrapping>) -> Wrapping> { - Wrapping(self.0.wrapping_sub(&rhs.0)) - } -} - -impl SubAssign for Wrapping> { - fn sub_assign(&mut self, other: Self) { - *self = *self - other; - } -} - -impl SubAssign<&Wrapping>> for Wrapping> { - fn sub_assign(&mut self, other: &Self) { - *self = *self - other; - } -} - -#[cfg(test)] -mod tests { - use crate::{Limb, U128}; - - #[test] - fn sbb_no_borrow() { - let (res, borrow) = U128::ONE.sbb(&U128::ONE, Limb::ZERO); - assert_eq!(res, U128::ZERO); - assert_eq!(borrow, Limb::ZERO); - } - - #[test] - fn sbb_with_borrow() { - let (res, borrow) = U128::ZERO.sbb(&U128::ONE, Limb::ZERO); - - assert_eq!(res, U128::MAX); - assert_eq!(borrow, Limb::MAX); - } - - #[test] - fn wrapping_sub_no_borrow() { - assert_eq!(U128::ONE.wrapping_sub(&U128::ONE), U128::ZERO); - } - - #[test] - fn wrapping_sub_with_borrow() { - assert_eq!(U128::ZERO.wrapping_sub(&U128::ONE), U128::MAX); - } - - #[test] - fn checked_sub_ok() { - let result = U128::ONE.checked_sub(&U128::ONE); - assert_eq!(result.unwrap(), U128::ZERO); - } - - #[test] - fn checked_sub_overflow() { - let result = U128::ZERO.checked_sub(&U128::ONE); - assert!(!bool::from(result.is_some())); - } -} diff --git a/crypto-bigint/src/uint/sub_mod.rs b/crypto-bigint/src/uint/sub_mod.rs deleted file mode 100644 index 2f111b85..00000000 --- a/crypto-bigint/src/uint/sub_mod.rs +++ /dev/null @@ -1,119 +0,0 @@ -//! [`UInt`] subtraction modulus operations. - -use crate::{Limb, SubMod, UInt}; - -impl UInt { - /// Computes `self - rhs mod p` in constant time. - /// - /// Assumes `self` and `rhs` are `< p`. - pub const fn sub_mod(&self, rhs: &UInt, p: &UInt) -> UInt { - let (mut out, borrow) = self.sbb(rhs, Limb::ZERO); - - // If underflow occurred on the final limb, borrow = 0xfff...fff, otherwise - // borrow = 0x000...000. Thus, we use it as a mask to conditionally add the modulus. - let mut carry = Limb::ZERO; - let mut i = 0; - - while i < LIMBS { - let (l, c) = out.limbs[i].adc(p.limbs[i].bitand(borrow), carry); - out.limbs[i] = l; - carry = c; - i += 1; - } - - out - } -} - -macro_rules! impl_sub_mod { - ($($size:expr),+) => { - $( - impl SubMod for UInt<$size> { - type Output = Self; - - fn sub_mod(&self, rhs: &Self, p: &Self) -> Self { - debug_assert!(self < p); - debug_assert!(rhs < p); - self.sub_mod(rhs, p) - } - } - )+ - }; -} - -impl_sub_mod!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); - -#[cfg(all(test, feature = "rand"))] -mod tests { - use crate::UInt; - - macro_rules! test_sub_mod { - ($size:expr, $test_name:ident) => { - #[test] - fn $test_name() { - use crate::Limb; - use rand_core::SeedableRng; - - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); - - let moduli = [UInt::<$size>::random(&mut rng), UInt::random(&mut rng)]; - - for p in &moduli { - let base_cases = [ - (1u64, 0u64, 1u64.into()), - (0, 1, p.wrapping_sub(&1u64.into())), - (0, 0, 0u64.into()), - ]; - for (a, b, c) in &base_cases { - let a: UInt<$size> = (*a).into(); - let b: UInt<$size> = (*b).into(); - - let x = a.sub_mod(&b, p); - assert_eq!(*c, x, "{} - {} mod {} = {} != {}", a, b, p, x, c); - } - - if $size > 1 { - for _i in 0..100 { - let a: UInt<$size> = Limb::random(&mut rng).into(); - let b: UInt<$size> = Limb::random(&mut rng).into(); - let (a, b) = if a < b { (b, a) } else { (a, b) }; - - let c = a.sub_mod(&b, p); - assert!(c < *p, "not reduced"); - assert_eq!(c, a.wrapping_sub(&b), "result incorrect"); - } - } - - for _i in 0..100 { - let a = UInt::<$size>::random_mod(&mut rng, p); - let b = UInt::<$size>::random_mod(&mut rng, p); - - let c = a.sub_mod(&b, p); - assert!(c < *p, "not reduced: {} >= {} ", c, p); - - let x = a.wrapping_sub(&b); - if a >= b && x < *p { - assert_eq!(c, x, "incorrect result"); - } - } - } - } - }; - } - - // Test requires 1-limb is capable of representing a 64-bit integer - #[cfg(target_pointer_width = "64")] - test_sub_mod!(1, sub1); - - test_sub_mod!(2, sub2); - test_sub_mod!(3, sub3); - test_sub_mod!(4, sub4); - test_sub_mod!(5, sub5); - test_sub_mod!(6, sub6); - test_sub_mod!(7, sub7); - test_sub_mod!(8, sub8); - test_sub_mod!(9, sub9); - test_sub_mod!(10, sub10); - test_sub_mod!(11, sub11); - test_sub_mod!(12, sub12); -} diff --git a/crypto-bigint/src/wrapping.rs b/crypto-bigint/src/wrapping.rs deleted file mode 100644 index 9bfc8712..00000000 --- a/crypto-bigint/src/wrapping.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Wrapping arithmetic. - -use core::fmt; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; - -/// Provides intentionally-wrapped arithmetic on `T`. -/// -/// This is analogous to [`core::num::Wrapping`] but allows this crate to -/// define trait impls for this type. -#[derive(Copy, Clone, Default, Eq, PartialEq, PartialOrd, Ord)] -pub struct Wrapping(pub T); - -impl fmt::Display for Wrapping { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Binary for Wrapping { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Octal for Wrapping { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::LowerHex for Wrapping { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::UpperHex for Wrapping { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl ConditionallySelectable for Wrapping { - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - Wrapping(T::conditional_select(&a.0, &b.0, choice)) - } -} - -impl ConstantTimeEq for Wrapping { - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } -} diff --git a/dbl/Cargo.toml b/dbl/Cargo.toml index 26558418..d683e840 100644 --- a/dbl/Cargo.toml +++ b/dbl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dbl" -version = "0.3.1" # Also update html_root_url in lib.rs when bumping this +version = "0.3.2" # Also update html_root_url in lib.rs when bumping this authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" description = "Double operation in Galois Field (GF)" diff --git a/dbl/src/lib.rs b/dbl/src/lib.rs index f9c22cf9..fe77c87c 100644 --- a/dbl/src/lib.rs +++ b/dbl/src/lib.rs @@ -1,16 +1,18 @@ +//! Double operation in Galois Field (GF) #![no_std] #![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/dbl/0.3.1" + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_root_url = "https://docs.rs/dbl/0.3.2" )] +#![forbid(unsafe_code)] extern crate generic_array; use generic_array::typenum::{U16, U32, U8}; use generic_array::GenericArray; -use core::mem; +use core::convert::TryInto; const C64: u64 = 0b1_1011; const C128: u64 = 0b1000_0111; @@ -27,48 +29,48 @@ pub trait Dbl { /// `block<<1`, otherwise `(block<<1)^C`, where `C` is the non-leading /// coefficients of the lexicographically first irreducible degree-b binary /// polynomial with the minimal number of ones. + #[must_use] fn dbl(self) -> Self; /// Reverse double block. (alternatively: divbide block by x) /// /// If least significant bit of the block equals to zero will return /// `block>>1`, otherwise `(block>>1)^(1<>1)` + #[must_use] fn inv_dbl(self) -> Self; } impl Dbl for GenericArray { + #[inline] fn dbl(self) -> Self { - let mut val: u64 = unsafe { mem::transmute_copy(&self) }; - val = val.to_be(); + let mut val = u64::from_be_bytes(self.into()); + let a = val >> 63; val <<= 1; val ^= a * C64; - unsafe { mem::transmute(val.to_be()) } + + val.to_be_bytes().into() } + #[inline] fn inv_dbl(self) -> Self { - let mut val: u64 = unsafe { mem::transmute_copy(&self) }; - val = val.to_be(); + let mut val = u64::from_be_bytes(self.into()); let a = val & 1; val >>= 1; val ^= a * ((1 << 63) ^ (C64 >> 1)); - unsafe { mem::transmute(val.to_be()) } - } -} - -#[inline(always)] -fn to_be(val: &mut [u64]) { - for v in val.iter_mut() { - *v = v.to_be(); + val.to_be_bytes().into() } } impl Dbl for GenericArray { + #[inline] fn dbl(self) -> Self { - let mut val: [u64; 2] = unsafe { mem::transmute_copy(&self) }; - to_be(&mut val); + let mut val = [ + u64::from_be_bytes(self[..8].try_into().unwrap()), + u64::from_be_bytes(self[8..].try_into().unwrap()), + ]; let b = val[1] >> 63; let a = val[0] >> 63; @@ -78,13 +80,18 @@ impl Dbl for GenericArray { val[1] <<= 1; val[1] ^= a * C128; - to_be(&mut val); - unsafe { mem::transmute(val) } + let mut res = Self::default(); + res[..8].copy_from_slice(&val[0].to_be_bytes()); + res[8..].copy_from_slice(&val[1].to_be_bytes()); + res } + #[inline] fn inv_dbl(self) -> Self { - let mut val: [u64; 2] = unsafe { mem::transmute_copy(&self) }; - to_be(&mut val); + let mut val = [ + u64::from_be_bytes(self[..8].try_into().unwrap()), + u64::from_be_bytes(self[8..].try_into().unwrap()), + ]; let a = (val[0] & 1) << 63; let b = val[1] & 1; @@ -95,15 +102,22 @@ impl Dbl for GenericArray { val[0] ^= b * (1 << 63); val[1] ^= b * (C128 >> 1); - to_be(&mut val); - unsafe { mem::transmute(val) } + let mut res = Self::default(); + res[..8].copy_from_slice(&val[0].to_be_bytes()); + res[8..].copy_from_slice(&val[1].to_be_bytes()); + res } } impl Dbl for GenericArray { + #[inline] fn dbl(self) -> Self { - let mut val: [u64; 4] = unsafe { mem::transmute_copy(&self) }; - to_be(&mut val); + let mut val = [ + u64::from_be_bytes(self[0..8].try_into().unwrap()), + u64::from_be_bytes(self[8..16].try_into().unwrap()), + u64::from_be_bytes(self[16..24].try_into().unwrap()), + u64::from_be_bytes(self[24..32].try_into().unwrap()), + ]; let a = val[0] >> 63; let b = val[1] >> 63; @@ -119,13 +133,22 @@ impl Dbl for GenericArray { val[3] <<= 1; val[3] ^= a * C256; - to_be(&mut val); - unsafe { mem::transmute(val) } + let mut res = Self::default(); + res[0..8].copy_from_slice(&val[0].to_be_bytes()); + res[8..16].copy_from_slice(&val[1].to_be_bytes()); + res[16..24].copy_from_slice(&val[2].to_be_bytes()); + res[24..32].copy_from_slice(&val[3].to_be_bytes()); + res } + #[inline] fn inv_dbl(self) -> Self { - let mut val: [u64; 4] = unsafe { mem::transmute_copy(&self) }; - to_be(&mut val); + let mut val = [ + u64::from_be_bytes(self[0..8].try_into().unwrap()), + u64::from_be_bytes(self[8..16].try_into().unwrap()), + u64::from_be_bytes(self[16..24].try_into().unwrap()), + u64::from_be_bytes(self[24..32].try_into().unwrap()), + ]; let a = (val[0] & 1) << 63; let b = (val[1] & 1) << 63; @@ -143,7 +166,11 @@ impl Dbl for GenericArray { val[0] ^= d * (1 << 63); val[3] ^= d * (C256 >> 1); - to_be(&mut val); - unsafe { mem::transmute(val) } + let mut res = Self::default(); + res[0..8].copy_from_slice(&val[0].to_be_bytes()); + res[8..16].copy_from_slice(&val[1].to_be_bytes()); + res[16..24].copy_from_slice(&val[2].to_be_bytes()); + res[24..32].copy_from_slice(&val[3].to_be_bytes()); + res } } diff --git a/der/CHANGELOG.md b/der/CHANGELOG.md deleted file mode 100644 index 881e49e9..00000000 --- a/der/CHANGELOG.md +++ /dev/null @@ -1,218 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.4.1 (2021-08-08) -### Fixed -- Encoding `UTCTime` for dates with `20xx` years ([#569]) - -[#569]: https://github.com/RustCrypto/utils/pull/569 - -## 0.4.0 (2021-06-07) -### Added -- `TagNumber` type ([#464]) -- Const generic integer de/encoders with support for all of Rust's integer - primitives ([#469], [#470]) -- `crypto-bigint` support ([#472]) -- `Tag` number helpers ([#477]) -- `Tag::octet` ([#479]) -- `ErrorKind::Value` helpers ([#481]) -- `SequenceIter` ([#483]) - -### Changed -- Bump `const-oid` crate dependency to v0.6 ([#463]) -- Make `Tag` structured ([#464]) -- Namespace ASN.1 types in `asn1` module ([#465]) -- Refactor context-specific field decoding ([#466]) -- MSRV 1.51 ([#469], [#470]) -- Rename `big-uint` crate feature to `bigint` ([#472]) -- Rename `BigUInt` to `UIntBytes` ([#473]) -- Have `Decoder::error()` return an `Error` ([#487]) - -### Removed -- Deprecated methods replaced by associated constants ([#458]) - -[#458]: https://github.com/RustCrypto/utils/pull/458 -[#463]: https://github.com/RustCrypto/utils/pull/463 -[#464]: https://github.com/RustCrypto/utils/pull/464 -[#465]: https://github.com/RustCrypto/utils/pull/465 -[#466]: https://github.com/RustCrypto/utils/pull/466 -[#469]: https://github.com/RustCrypto/utils/pull/469 -[#470]: https://github.com/RustCrypto/utils/pull/470 -[#472]: https://github.com/RustCrypto/utils/pull/472 -[#473]: https://github.com/RustCrypto/utils/pull/473 -[#477]: https://github.com/RustCrypto/utils/pull/477 -[#479]: https://github.com/RustCrypto/utils/pull/479 -[#481]: https://github.com/RustCrypto/utils/pull/481 -[#483]: https://github.com/RustCrypto/utils/pull/483 -[#487]: https://github.com/RustCrypto/utils/pull/487 - -## 0.3.5 (2021-05-24) -### Added -- Helper methods for context-specific fields ([#422], [#423], [#428], [#429]) -- `ContextSpecific` field wrapper ([#428]) -- Decoder position tracking for errors during `Any<'a>` decoding ([#431]) - -### Fixed -- `From` conversion for `BitString` into `Any` ([#428]) - -[#422]: https://github.com/RustCrypto/utils/pull/422 -[#423]: https://github.com/RustCrypto/utils/pull/423 -[#428]: https://github.com/RustCrypto/utils/pull/428 -[#429]: https://github.com/RustCrypto/utils/pull/429 -[#431]: https://github.com/RustCrypto/utils/pull/431 - -## 0.3.4 (2021-05-16) -### Changed -- Support `Length` of up to 1 MiB ([#411]) - -[#411]: https://github.com/RustCrypto/utils/pull/411 - -## 0.3.3 (2021-04-15) -### Added -- `Length` constants ([#371]) - -### Changed -- Deprecate `const fn` methods replaced by `Length` constants ([#371]) - -[#371]: https://github.com/RustCrypto/utils/pull/371 - -## 0.3.2 (2021-04-15) -### Fixed -- Non-critical bug allowing `Length` to exceed the max invariant ([#367]) - -[#367]: https://github.com/RustCrypto/utils/pull/367 - -## 0.3.1 (2021-04-01) [YANKED] -### Added -- `PartialOrd` + `Ord` impls to all ASN.1 types ([#363]) - -[#363]: https://github.com/RustCrypto/utils/pull/363 - -## 0.3.0 (2021-03-22) [YANKED] -### Added -- Impl `Decode`/`Encoded`/`Tagged` for `String` ([#344]) -- `Length::one` and `Length::for_tlv` ([#351]) -- `SET OF` support with `SetOf` trait and `SetOfRef` ([#346], [#352]) - -### Changed -- Rename `Decodable::from_bytes` => `Decodable::from_der` ([#339]) -- Separate `sequence` and `message` ([#341]) -- Rename `ErrorKind::Oid` => `ErrorKind::MalformedOid` ([#342]) -- Auto-derive `From` impls for variants when deriving `Choice` ([#345]) -- Make `Length` use `u32` internally ([#349]) -- Make `Sequence` constructor private ([#348]) -- Bump `const_oid` to v0.5 ([#350]) -- Bump `der_derive` to v0.3 ([#353]) - -### Removed -- Deprecated methods ([#340]) -- `BigUIntSize` ([#347]) - -[#339]: https://github.com/RustCrypto/utils/pull/339 -[#340]: https://github.com/RustCrypto/utils/pull/340 -[#341]: https://github.com/RustCrypto/utils/pull/341 -[#342]: https://github.com/RustCrypto/utils/pull/342 -[#344]: https://github.com/RustCrypto/utils/pull/344 -[#345]: https://github.com/RustCrypto/utils/pull/345 -[#346]: https://github.com/RustCrypto/utils/pull/346 -[#347]: https://github.com/RustCrypto/utils/pull/347 -[#348]: https://github.com/RustCrypto/utils/pull/348 -[#349]: https://github.com/RustCrypto/utils/pull/349 -[#350]: https://github.com/RustCrypto/utils/pull/350 -[#351]: https://github.com/RustCrypto/utils/pull/351 -[#352]: https://github.com/RustCrypto/utils/pull/352 -[#353]: https://github.com/RustCrypto/utils/pull/353 - -## 0.2.10 (2021-02-28) -### Added -- Impl `From` for `Any` ([#317], [#319]) - -### Changed -- Bump minimum `const-oid` dependency to v0.4.4 ([#318]) - -[#317]: https://github.com/RustCrypto/utils/pull/317 -[#318]: https://github.com/RustCrypto/utils/pull/318 -[#319]: https://github.com/RustCrypto/utils/pull/319 - -## 0.2.9 (2021-02-24) -### Added -- Support for `IA5String` ([#310]) - -[#310]: https://github.com/RustCrypto/utils/pull/310 - -## 0.2.8 (2021-02-22) -### Added -- `Choice` trait ([#295]) - -[#295]: https://github.com/RustCrypto/utils/pull/295 - -## 0.2.7 (2021-02-20) -### Added -- Export `Header` publicly ([#283]) -- Make `Encoder::reserve` public ([#285]) - -[#283]: https://github.com/RustCrypto/utils/pull/283 -[#285]: https://github.com/RustCrypto/utils/pull/285 - -## 0.2.6 (2021-02-19) -### Added -- Make the unit type an encoding of `NULL` ([#281]) - -[#281]: https://github.com/RustCrypto/utils/pull/281 - -## 0.2.5 (2021-02-18) -### Added -- `ErrorKind::UnknownOid` variant ([#273], [#275]) - -[#273]: https://github.com/RustCrypto/utils/pull/273 -[#275]: https://github.com/RustCrypto/utils/pull/275 - -## 0.2.4 (2021-02-16) -### Added -- `Any::is_null` method ([#262]) - -### Changed -- Deprecate `Any::null` method ([#262]) - -[#262]: https://github.com/RustCrypto/utils/pull/262 - -## 0.2.3 (2021-02-15) -### Added -- Additional `rustdoc` documentation ([#252], [#256]) - -[#252]: https://github.com/RustCrypto/utils/pull/252 -[#256]: https://github.com/RustCrypto/utils/pull/256 - -## 0.2.2 (2021-02-12) -### Added -- Support for `UTCTime` and `GeneralizedTime` ([#250]) - -[#250]: https://github.com/RustCrypto/utils/pull/250 - -## 0.2.1 (2021-02-02) -### Added -- Support for `PrintableString` and `Utf8String` ([#245]) - -[#245]: https://github.com/RustCrypto/utils/pull/245 - -## 0.2.0 (2021-01-22) -### Added -- `BigUInt` type ([#196]) -- `i16` support ([#199]) -- `u8` and `u16` support ([#210]) -- Integer decoder helper methods ([#219]) - -### Fixed -- Handle leading byte of `BIT STRING`s ([#193]) - -[#193]: https://github.com/RustCrypto/utils/pull/193 -[#196]: https://github.com/RustCrypto/utils/pull/196 -[#199]: https://github.com/RustCrypto/utils/pull/199 -[#210]: https://github.com/RustCrypto/utils/pull/210 -[#219]: https://github.com/RustCrypto/utils/pull/219 - -## 0.1.0 (2020-12-21) -- Initial release diff --git a/der/Cargo.toml b/der/Cargo.toml deleted file mode 100644 index 157da77e..00000000 --- a/der/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "der" -version = "0.4.1" # Also update html_root_url in lib.rs when bumping this -description = """ -Pure Rust embedded-friendly implementation of the Distinguished Encoding Rules -(DER) for Abstract Syntax Notation One (ASN.1) as described in ITU X.690 with -full support for heapless no_std targets -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -repository = "https://github.com/RustCrypto/utils/tree/master/der" -categories = ["cryptography", "data-structures", "encoding", "no-std", "parser-implementations"] -keywords = ["asn1", "crypto", "itu", "pkcs"] -readme = "README.md" - -[dependencies] -const-oid = { version = "0.6", optional = true, path = "../const-oid" } -crypto-bigint = { version = "0.2", optional = true, features = ["generic-array"], path = "../crypto-bigint" } -der_derive = { version = "0.4", optional = true, path = "derive" } - -[dev-dependencies] -hex-literal = "0.3" - -[features] -alloc = [] -derive = ["der_derive"] -bigint = ["crypto-bigint"] -oid = ["const-oid"] -std = ["alloc"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/der/LICENSE-APACHE b/der/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/der/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/der/LICENSE-MIT b/der/LICENSE-MIT deleted file mode 100644 index 2726e14a..00000000 --- a/der/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2020 The RustCrypto Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/der/README.md b/der/README.md deleted file mode 100644 index 67fe7784..00000000 --- a/der/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# RustCrypto: ASN.1 DER - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Pure Rust embedded-friendly implementation of the Distinguished Encoding Rules (DER) -for Abstract Syntax Notation One (ASN.1) as described in ITU X.690. - -[Documentation][docs-link] - -# About - -This crate provides a `no_std`-friendly implementation of a subset of ASN.1 DER -necessary for decoding/encoding various cryptography-related formats -implemented as part of the [RustCrypto] project, e.g. the [`pkcs8`] crate. - -The core implementation avoids any heap usage (with convenience methods -that allocate gated under the off-by-default `alloc` feature). - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/der.svg -[crate-link]: https://crates.io/crates/der -[docs-image]: https://docs.rs/der/badge.svg -[docs-link]: https://docs.rs/der/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/actions/workflows/der.yml/badge.svg -[build-link]: https://github.com/RustCrypto/utils/actions/workflows/der.yml - -[//]: # (general links) - -[RustCrypto]: https://github.com/rustcrypto -[`pkcs8`]: https://docs.rs/pkcs8/ -[RustCrypto/utils#370]: https://github.com/RustCrypto/utils/issues/370 diff --git a/der/derive/CHANGELOG.md b/der/derive/CHANGELOG.md deleted file mode 100644 index 588a4334..00000000 --- a/der/derive/CHANGELOG.md +++ /dev/null @@ -1,43 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.4.0 (2021-06-07) -### Changed -- Generated code updates which ensure compatibility with upstream `der` crate - changes ([#464], [#465], [#481]) - -[#464]: https://github.com/RustCrypto/utils/pull/464 -[#465]: https://github.com/RustCrypto/utils/pull/465 -[#481]: https://github.com/RustCrypto/utils/pull/481 - -## 0.3.0 (2021-03-21) -### Added -- `choice::Alternative` and duplicate tracking ([#300]) -- Auto-derive `From` impls for variants when deriving `Choice` ([#345]) - -[#300]: https://github.com/RustCrypto/utils/pull/300 -[#345]: https://github.com/RustCrypto/utils/pull/345 - -## 0.2.2 (2021-02-22) -### Added -- Custom derive support for the `Choice` trait ([#296]) - -[#296]: https://github.com/RustCrypto/utils/pull/296 - -## 0.2.1 (2021-02-15) -### Added -- Custom derive support for enums ([#254]) - -[#254]: https://github.com/RustCrypto/utils/pull/254 - -## 0.2.0 (2021-02-02) -### Added -- Support for `PrintableString` and `Utf8String` ([#245]) - -[#245]: https://github.com/RustCrypto/utils/pull/245 - -## 0.1.0 (2020-12-21) -- Initial release diff --git a/der/derive/Cargo.toml b/der/derive/Cargo.toml deleted file mode 100644 index 290a8a78..00000000 --- a/der/derive/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "der_derive" -version = "0.4.0" # Also update html_root_url in lib.rs when bumping this -description = """ -Procedural macro for automatically deriving the `der` crate's `Choice` and -`Message` traits -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -documentation = "https://docs.rs/der" -repository = "https://github.com/RustCrypto/utils/tree/master/der" -categories = ["cryptography", "data-structures", "encoding", "no-std"] -keywords = ["asn1", "der", "crypto", "itu", "pkcs"] -readme = "README.md" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1" -quote = "1" -syn = "1" -synstructure = "0.12" diff --git a/der/derive/README.md b/der/derive/README.md deleted file mode 100644 index e22c58c0..00000000 --- a/der/derive/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# RustCrypto: DER Custom Derive Support - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Procedural macro for automatically deriving the `der` crate's `Choice` and -`Message` traits. - -[Documentation][docs-link] - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/der_derive.svg -[crate-link]: https://crates.io/crates/der_derive -[docs-image]: https://docs.rs/der_derive/badge.svg -[docs-link]: https://docs.rs/der_derive/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.46+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/der/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions?query=workflow:der diff --git a/der/derive/src/attributes.rs b/der/derive/src/attributes.rs deleted file mode 100644 index bf22bade..00000000 --- a/der/derive/src/attributes.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Attribute-related types used by the proc macro - -use crate::Asn1Type; -use syn::{Attribute, Lit, Meta, MetaList, MetaNameValue, NestedMeta}; - -#[derive(Debug)] -pub(crate) struct Asn1Attrs { - /// Value of the `#[asn1(type = "...")]` attribute if provided - pub asn1_type: Option, -} - -impl Asn1Attrs { - /// Parse attributes from a field or enum variant - pub fn new(attrs: &[Attribute]) -> Self { - let mut asn1_type = None; - - for attr in attrs { - if !attr.path.is_ident("asn1") { - continue; - } - - match attr.parse_meta().expect("error parsing `asn1` attribute") { - Meta::List(MetaList { nested, .. }) if nested.len() == 1 => { - match nested.first() { - Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, - lit: Lit::Str(lit_str), - .. - }))) => { - // Parse the `type = "..."` attribute - if !path.is_ident("type") { - panic!("unknown `asn1` attribute: {:?}", path); - } - - if let Some(ty) = asn1_type { - panic!("duplicate ASN.1 `type` attribute: {:?}", ty); - } - - asn1_type = Some(Asn1Type::new(&lit_str.value())); - } - other => panic!("malformed `asn1` attribute: {:?}", other), - } - } - other => panic!("malformed `asn1` attribute: {:?}", other), - } - } - - Self { asn1_type } - } -} diff --git a/der/derive/src/choice.rs b/der/derive/src/choice.rs deleted file mode 100644 index f7a58981..00000000 --- a/der/derive/src/choice.rs +++ /dev/null @@ -1,256 +0,0 @@ -//! Support for deriving the `Decodable` and `Encodable` traits on enums for -//! the purposes of decoding/encoding ASN.1 `CHOICE` types as mapped to -//! enum variants. - -use crate::{Asn1Attrs, Asn1Type}; -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::{DataEnum, Fields, FieldsUnnamed, Ident, Lifetime, Type, Variant}; -use synstructure::{Structure, VariantInfo}; - -/// Registry of `CHOICE` alternatives for a given enum -type Alternatives = std::collections::BTreeMap; - -/// Derive the `Choice` trait for an enum. -pub(crate) struct DeriveChoice { - /// `CHOICE` alternatives for this enum. - alternatives: Alternatives, - - /// Tags included in the impl body for `der::Choice`. - choice_body: TokenStream, - - /// Enum match arms for the impl body for `TryFrom>`. - decode_body: TokenStream, - - /// Enum match arms for the impl body for `der::Encodable::encode`. - encode_body: TokenStream, - - /// Enum match arms for the impl body for `der::Encodable::encoded_len`. - encoded_len_body: TokenStream, -} - -impl DeriveChoice { - /// Derive `Decodable` on an enum. - pub fn derive(s: Structure<'_>, data: &DataEnum, lifetime: Option<&Lifetime>) -> TokenStream { - assert_eq!( - s.variants().len(), - data.variants.len(), - "enum variant count mismatch" - ); - - let mut state = Self { - alternatives: Default::default(), - choice_body: TokenStream::new(), - decode_body: TokenStream::new(), - encode_body: TokenStream::new(), - encoded_len_body: TokenStream::new(), - }; - - for (variant_info, variant) in s.variants().iter().zip(&data.variants) { - let asn1_type = Asn1Attrs::new(&variant.attrs).asn1_type.unwrap_or_else(|| { - panic!( - "no #[asn1(type=...)] specified for enum variant: {}", - variant.ident - ) - }); - - Alternative::register(&mut state.alternatives, asn1_type, variant); - state.derive_variant_choice(asn1_type); - state.derive_variant_decoder(asn1_type); - - match variant_info.bindings().len() { - // TODO(tarcieri): handle 0 bindings for ASN.1 NULL - 1 => { - state.derive_variant_encoder(&variant_info, asn1_type); - state.derive_variant_encoded_len(&variant_info); - } - other => panic!( - "unsupported number of ASN.1 variant bindings for {}: {}", - asn1_type, other - ), - } - } - - state.finish(s, lifetime) - } - - /// Derive the body of `Choice::can_decode - fn derive_variant_choice(&mut self, asn1_type: Asn1Type) { - let tag = asn1_type.tag(); - - if self.choice_body.is_empty() { - tag - } else { - quote!(| #tag) - } - .to_tokens(&mut self.choice_body); - } - - /// Derive a match arm of the impl body for `TryFrom>`. - fn derive_variant_decoder(&mut self, asn1_type: Asn1Type) { - let tag = asn1_type.tag(); - - let decoder = match asn1_type { - Asn1Type::BitString => quote!(any.bit_string()), - Asn1Type::GeneralizedTime => quote!(any.generalized_time()), - Asn1Type::OctetString => quote!(any.octet_string()), - Asn1Type::PrintableString => quote!(any.printable_string()), - Asn1Type::UtcTime => quote!(any.utc_time()), - Asn1Type::Utf8String => quote!(any.utf8_string()), - }; - - { - quote! { - #tag => #decoder - .ok() - .and_then(|val| val.try_into().ok()) - .ok_or_else(|| #tag.value_error()), - } - } - .to_tokens(&mut self.decode_body); - } - - /// Derive a match arm for the impl body for `der::Encodable::encode`. - fn derive_variant_encoder(&mut self, variant: &VariantInfo<'_>, asn1_type: Asn1Type) { - assert_eq!( - variant.bindings().len(), - 1, - "unexpected number of variant bindings" - ); - - variant - .each(|bi| { - let binding = &bi.binding; - let encoder_obj = asn1_type.encoder(quote!(#binding)); - quote!(#encoder_obj?.encode(encoder)) - }) - .to_tokens(&mut self.encode_body); - } - - /// Derive a match arm for the impl body for `der::Encodable::encode`. - fn derive_variant_encoded_len(&mut self, variant: &VariantInfo<'_>) { - assert_eq!( - variant.bindings().len(), - 1, - "unexpected number of variant bindings" - ); - - variant - .each(|bi| { - let binding = &bi.binding; - quote!(#binding.encoded_len()) - }) - .to_tokens(&mut self.encoded_len_body); - } - - /// Finish deriving an enum - fn finish(self, s: Structure<'_>, lifetime: Option<&Lifetime>) -> TokenStream { - let lifetime = match lifetime { - Some(lifetime) => quote!(#lifetime), - None => quote!('_), - }; - - let Self { - choice_body, - decode_body, - encode_body, - encoded_len_body, - .. - } = self; - - let mut variant_conversions = TokenStream::new(); - - for variant in self.alternatives.values() { - let variant_ident = &variant.ident; - let variant_type = &variant.field_type; - - variant_conversions.extend(s.gen_impl(quote! { - gen impl From<#variant_type> for @Self { - fn from(field: #variant_type) -> Self { - Self::#variant_ident(field) - } - } - })); - } - - s.gen_impl(quote! { - gen impl ::der::Choice<#lifetime> for @Self { - fn can_decode(tag: ::der::Tag) -> bool { - matches!(tag, #choice_body) - } - } - - gen impl core::convert::TryFrom<::der::asn1::Any<#lifetime>> for @Self { - type Error = der::Error; - - fn try_from(any: ::der::asn1::Any<#lifetime>) -> der::Result { - #[allow(unused_imports)] - use core::convert::TryInto; - - match any.tag() { - #decode_body - actual => Err(der::ErrorKind::UnexpectedTag { - expected: None, - actual - } - .into()), - } - } - } - - gen impl ::der::Encodable for @Self { - fn encode(&self, encoder: &mut ::der::Encoder<'_>) -> ::der::Result<()> { - #[allow(unused_imports)] - use core::convert::TryFrom; - - match self { - #encode_body - } - } - - fn encoded_len(&self) -> ::der::Result<::der::Length> { - match self { - #encoded_len_body - } - } - } - - #variant_conversions - }) - } -} - -/// ASN.1 `CHOICE` alternative: one of the ASN.1 types comprising the `CHOICE` -/// which maps to an enum variant. -struct Alternative { - /// [`Ident`] for the corresponding enum variant. - pub ident: Ident, - - /// Type of the inner field (i.e. of the variant's 1-tuple) - pub field_type: Type, -} - -impl Alternative { - /// Register a `CHOICE` alternative for a variant - pub fn register(alternatives: &mut Alternatives, asn1_type: Asn1Type, variant: &Variant) { - let field_type = match &variant.fields { - Fields::Unnamed(FieldsUnnamed { unnamed, .. }) if unnamed.len() == 1 => { - let field = unnamed.first().unwrap(); - field.ty.clone() - } - _ => panic!("can only derive `Choice` for enums with 1-tuple variants"), - }; - - let alternative = Self { - ident: variant.ident.clone(), - field_type, - }; - - if let Some(duplicate) = alternatives.insert(asn1_type, alternative) { - panic!( - "duplicate ASN.1 type `{}` for enum variants `{}` and `{}`", - asn1_type, duplicate.ident, variant.ident - ); - } - } -} diff --git a/der/derive/src/lib.rs b/der/derive/src/lib.rs deleted file mode 100644 index 8a4a80ef..00000000 --- a/der/derive/src/lib.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! Custom derive support for the [`der`] crate. -//! -//! This crate contains custom derive macros intended to be used in the -//! following way: -//! -//! - [`Choice`][`derive@Choice`]: map ASN.1 `CHOICE` to a Rust enum. -//! - [`Message`][`derive@Message`]: map ASN.1 `SEQUENCE` to a Rust struct. -//! -//! Note that this crate shouldn't be used directly, but instead accessed -//! by using the `derive` feature of the `der` crate. -//! -//! # Why not `serde`? -//! -//! The `der` crate is designed to be easily usable in embedded environments, -//! including ones where code size comes at a premium. -//! -//! This crate (i.e. `der_derive`) is able to generate code which is -//! significantly smaller than `serde_derive`. This is because the `der` -//! crate has been designed with high-level abstractions which reduce -//! code size, including trait object-based encoders which allow encoding -//! logic which is duplicated in `serde` serializers to be implemented in -//! a single place in the `der` crate. -//! -//! This is a deliberate tradeoff in terms of performance, flexibility, and -//! code size. At least for now, the `der` crate is optimizing for leveraging -//! as many abstractions as it can to minimize code size. -//! -//! # `#[asn1(type = "...")]` attribute -//! -//! This attribute can be used to specify the ASN.1 type for a particular -//! enum variant or struct field. -//! -//! It's presently mandatory for all enum variants, even when using one of the -//! ASN.1 types defined by this crate. -//! -//! For structs, placing this attribute on a field makes it possible to -//! decode/encode types which don't directly implement the `Decode`/`Encode` -//! traits but do impl `From` and `TryInto` and `From` for one of the ASN.1 types -//! listed below (use the ASN.1 type keywords as the `type`): -//! -//! - `BIT STRING`: performs an intermediate conversion to [`der::asn1::BitString`] -//! - `GeneralizedTime`: performs an intermediate conversion to [`der::asn1::GeneralizedTime`] -//! - `OCTET STRING`: performs an intermediate conversion to [`der::asn1::OctetString`] -//! - `PrintableString`: performs an intermediate conversion to [`der::asn1::PrintableString`] -//! - `UTCTime`: performs an intermediate conversion to [`der::asn1::UtcTime`] -//! - `UTF8String`: performs an intermediate conversion to [`der::asn1::Utf8String`] -//! -//! Note: please open a GitHub Issue if you would like to request support -//! for additional ASN.1 types. -//! -//! [`der`]: https://docs.rs/der/ -//! [`der::asn1::BitString`]: https://docs.rs/der/latest/der/asn1/struct.BitString.html -//! [`der::asn1::GeneralizedTime`]: https://docs.rs/der/latest/der/asn1/struct.GeneralizedTime.html -//! [`der::asn1::OctetString`]: https://docs.rs/der/latest/der/asn1/struct.OctetString.html -//! [`der::asn1::PrintableString`]: https://docs.rs/der/latest/der/asn1/struct.PrintableString.html -//! [`der::asn1::UtcTime`]: https://docs.rs/der/latest/der/asn1/struct.UtcTime.html -//! [`der::asn1::Utf8String`]: https://docs.rs/der/latest/der/asn1/struct.Utf8String.html - -#![crate_type = "proc-macro"] -#![warn(rust_2018_idioms, trivial_casts, unused_qualifications)] - -mod attributes; -mod choice; -mod message; -mod types; - -use crate::{attributes::Asn1Attrs, choice::DeriveChoice, message::DeriveMessage, types::Asn1Type}; -use proc_macro2::TokenStream; -use syn::{Generics, Lifetime}; -use synstructure::{decl_derive, Structure}; - -decl_derive!( - [Choice, attributes(asn1)] => - - /// Derive the [`Choice`][1] trait on an enum. - /// - /// This custom derive macro can be used to automatically impl the - /// [`Decodable`][2] and [`Encodable`][3] traits along with the - /// [`Choice`][1] supertrait for any enum representing an ASN.1 `CHOICE`. - /// - /// The enum must consist entirely of 1-tuple variants wrapping inner - /// types which must also impl the [`Decodable`][2] and [`Encodable`][3] - /// traits. It will will also generate [`From`] impls for each of the - /// inner types of the variants into the enum that wraps them. - /// - /// # Usage - /// - /// ```ignore - /// // NOTE: requires the `derive` feature of `der` - /// use der::Choice; - /// - /// /// `Time` as defined in RFC 5280 - /// #[derive(Choice)] - /// pub enum Time { - /// #[asn1(type = "UTCTime")] - /// UtcTime(UtcTime), - /// - /// #[asn1(type = "GeneralizedTime")] - /// GeneralTime(GeneralizedTime), - /// } - /// ``` - /// - /// # `#[asn1(type = "...")]` attribute - /// - /// See [toplevel documentation for the `der_derive` crate][4] for more - /// information about the `#[asn1]` attribute. - /// - /// [1]: https://docs.rs/der/latest/der/trait.Choice.html - /// [2]: https://docs.rs/der/latest/der/trait.Decodable.html - /// [3]: https://docs.rs/der/latest/der/trait.Encodable.html - /// [4]: https://docs.rs/der_derive/ - derive_choice -); - -decl_derive!( - [Message, attributes(asn1)] => - - /// Derive the [`Message`][1] trait on a struct. - /// - /// This custom derive macro can be used to automatically impl the - /// `Message` trait for any struct representing a message which is - /// encoded as an ASN.1 `SEQUENCE`. - /// - /// # Usage - /// - /// ```ignore - /// use der::{ - /// asn1::{Any, ObjectIdentifier}, - /// Message - /// }; - /// - /// /// X.509 `AlgorithmIdentifier` - /// #[derive(Message)] - /// pub struct AlgorithmIdentifier<'a> { - /// /// This field contains an ASN.1 `OBJECT IDENTIFIER`, a.k.a. OID. - /// pub algorithm: ObjectIdentifier, - /// - /// /// This field is `OPTIONAL` and contains the ASN.1 `ANY` type, which - /// /// in this example allows arbitrary algorithm-defined parameters. - /// pub parameters: Option> - /// } - /// ``` - /// - /// # `#[asn1(type = "...")]` attribute - /// - /// See [toplevel documentation for the `der_derive` crate][2] for more - /// information about the `#[asn1]` attribute. - /// - /// [1]: https://docs.rs/der/latest/der/trait.Message.html - /// [2]: https://docs.rs/der_derive/ - derive_message -); - -/// Custom derive for `der::Choice` -fn derive_choice(s: Structure<'_>) -> TokenStream { - let ast = s.ast(); - let lifetime = parse_lifetime(&ast.generics); - - match &ast.data { - syn::Data::Enum(data) => DeriveChoice::derive(s, data, lifetime), - other => panic!("can't derive `Choice` on: {:?}", other), - } -} - -/// Custom derive for `der::Message` -fn derive_message(s: Structure<'_>) -> TokenStream { - let ast = s.ast(); - let lifetime = parse_lifetime(&ast.generics); - - match &ast.data { - syn::Data::Struct(data) => DeriveMessage::derive(s, data, lifetime), - other => panic!("can't derive `Message` on: {:?}", other), - } -} - -/// Parse the first lifetime of the "self" type of the custom derive -/// -/// Returns `None` if there is no first lifetime. -fn parse_lifetime(generics: &Generics) -> Option<&Lifetime> { - generics - .lifetimes() - .next() - .map(|ref lt_ref| <_ref.lifetime) -} diff --git a/der/derive/src/message.rs b/der/derive/src/message.rs deleted file mode 100644 index 9e10a361..00000000 --- a/der/derive/src/message.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! Support for deriving the `Message` trait on structs for the purposes of -//! decoding/encoding ASN.1 `SEQUENCE` types as mapped to struct fields. - -use crate::{Asn1Attrs, Asn1Type}; -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::{DataStruct, Field, Ident, Lifetime}; -use synstructure::Structure; - -/// Derive the `Message` trait for a struct -pub(crate) struct DeriveMessage { - /// Field decoders - decode_fields: TokenStream, - - /// Bound fields of a struct to be returned - decode_result: TokenStream, - - /// Fields of a struct to be serialized - encode_fields: TokenStream, -} - -impl DeriveMessage { - pub fn derive(s: Structure<'_>, data: &DataStruct, lifetime: Option<&Lifetime>) -> TokenStream { - let mut state = Self { - decode_fields: TokenStream::new(), - decode_result: TokenStream::new(), - encode_fields: TokenStream::new(), - }; - - for field in &data.fields { - state.derive_field(field); - } - - state.finish(&s, lifetime) - } - - /// Derive handling for a particular `#[field(...)]` - fn derive_field(&mut self, field: &Field) { - let name = field - .ident - .as_ref() - .cloned() - .expect("no name on struct field i.e. tuple structs unsupported"); - - let asn1_type = Asn1Attrs::new(&field.attrs).asn1_type; - self.derive_field_decoder(&name, asn1_type); - self.derive_field_encoder(&name, asn1_type); - } - - /// Derive code for decoding a field of a message - fn derive_field_decoder(&mut self, name: &Ident, asn1_type: Option) { - let field_decoder = match asn1_type { - Some(Asn1Type::BitString) => quote! { - let #name = decoder.bit_string()?.try_into()?; - }, - Some(Asn1Type::GeneralizedTime) => quote! { - let #name = decoder.generalized_time()?.try_into()?; - }, - Some(Asn1Type::OctetString) => quote! { - let #name = decoder.octet_string()?.try_into()?; - }, - Some(Asn1Type::PrintableString) => quote! { - let #name = decoder.printable_string()?.try_into()?; - }, - Some(Asn1Type::UtcTime) => quote! { - let #name = decoder.utc_time()?.try_into()?; - }, - Some(Asn1Type::Utf8String) => quote! { - let #name = decoder.utf8_string()?.try_into()?; - }, - None => quote! { let #name = decoder.decode()?; }, - }; - field_decoder.to_tokens(&mut self.decode_fields); - - let field_result = quote!(#name,); - field_result.to_tokens(&mut self.decode_result); - } - - /// Derive code for encoding a field of a message - fn derive_field_encoder(&mut self, name: &Ident, asn1_type: Option) { - let binding = quote!(&self.#name); - asn1_type - .map(|ty| { - let encoder = ty.encoder(binding.clone()); - quote!(&#encoder?,) - }) - .unwrap_or_else(|| quote!(#binding,)) - .to_tokens(&mut self.encode_fields); - } - - /// Finish deriving a struct - fn finish(self, s: &Structure<'_>, lifetime: Option<&Lifetime>) -> TokenStream { - let lifetime = match lifetime { - Some(lifetime) => quote!(#lifetime), - None => quote!('_), - }; - - let decode_fields = self.decode_fields; - let decode_result = self.decode_result; - let encode_fields = self.encode_fields; - - s.gen_impl(quote! { - gen impl core::convert::TryFrom<::der::asn1::Any<#lifetime>> for @Self { - type Error = ::der::Error; - - fn try_from(any: ::der::asn1::Any<#lifetime>) -> ::der::Result { - #[allow(unused_imports)] - use core::convert::TryInto; - - any.sequence(|decoder| { - #decode_fields - Ok(Self { #decode_result }) - }) - } - } - - gen impl ::der::Message<#lifetime> for @Self { - fn fields(&self, f: F) -> ::der::Result - where - F: FnOnce(&[&dyn der::Encodable]) -> ::der::Result, - { - #[allow(unused_imports)] - use core::convert::TryFrom; - - f(&[#encode_fields]) - } - } - }) - } -} diff --git a/der/derive/src/types.rs b/der/derive/src/types.rs deleted file mode 100644 index 49078850..00000000 --- a/der/derive/src/types.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! ASN.1 types supported by the proc macro - -use core::fmt; -use proc_macro2::TokenStream; -use quote::quote; - -/// ASN.1 built-in types supported by the `#[asn1(type = "...")]` attribute -// TODO(tarcieri): support all ASN.1 types specified in `der::Tag` -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub(crate) enum Asn1Type { - /// ASN.1 `BIT STRING` - BitString, - - /// ASN.1 `GeneralizedTime` - GeneralizedTime, - - /// ASN.1 `OCTET STRING` - OctetString, - - /// ASN.1 `PrintableString` - PrintableString, - - /// ASN.1 `UTCTime` - UtcTime, - - /// ASN.1 `UTF8String` - Utf8String, -} - -impl Asn1Type { - /// Parse ASN.1 type - pub fn new(s: &str) -> Self { - match s { - "BIT STRING" => Self::BitString, - "GeneralizedTime" => Self::GeneralizedTime, - "OCTET STRING" => Self::OctetString, - "PrintableString" => Self::PrintableString, - "UTCTime" => Self::UtcTime, - "UTF8String" => Self::Utf8String, - _ => panic!("unrecognized ASN.1 type: {}", s), - } - } - - /// Get the `::der::Tag` for this ASN.1 type - pub fn tag(&self) -> TokenStream { - match self { - Asn1Type::BitString => quote!(::der::Tag::BitString), - Asn1Type::GeneralizedTime => quote!(::der::Tag::GeneralizedTime), - Asn1Type::OctetString => quote!(::der::Tag::OctetString), - Asn1Type::PrintableString => quote!(::der::Tag::PrintableString), - Asn1Type::UtcTime => quote!(::der::Tag::UtcTime), - Asn1Type::Utf8String => quote!(::der::Tag::Utf8String), - } - } - - /// Get a `der::Encoder` object for a particular ASN.1 type - pub fn encoder(&self, binding: TokenStream) -> TokenStream { - match self { - Asn1Type::BitString => quote!(::der::asn1::BitString::new(#binding)), - Asn1Type::GeneralizedTime => quote!(::der::asn1::GeneralizedTime::try_from(#binding)), - Asn1Type::OctetString => quote!(::der::asn1::OctetString::new(#binding)), - Asn1Type::PrintableString => quote!(::der::asn1::PrintableString::new(#binding)), - Asn1Type::UtcTime => quote!(::der::asn1::UtcTime::try_from(#binding)), - Asn1Type::Utf8String => quote!(&::der::Utf8String::new(#binding)), - } - } -} - -impl fmt::Display for Asn1Type { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Asn1Type::BitString => "BIT STRING", - Asn1Type::GeneralizedTime => "GeneralizedTime", - Asn1Type::OctetString => "OCTET STRING", - Asn1Type::PrintableString => "PrintableString", - Asn1Type::UtcTime => "UTCTime", - Asn1Type::Utf8String => "UTF8String", - }) - } -} diff --git a/der/src/asn1.rs b/der/src/asn1.rs deleted file mode 100644 index 512b5ee3..00000000 --- a/der/src/asn1.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! ASN.1 built-in types. - -mod any; -mod bit_string; -mod boolean; -mod context_specific; -mod generalized_time; -mod ia5_string; -mod integer; -mod null; -mod octet_string; -#[cfg(feature = "oid")] -mod oid; -mod optional; -mod printable_string; -mod sequence; -mod set_of; -mod utc_time; -mod utf8_string; - -pub use self::{ - any::Any, - bit_string::BitString, - context_specific::ContextSpecific, - generalized_time::GeneralizedTime, - ia5_string::Ia5String, - integer::bigint::UIntBytes, - null::Null, - octet_string::OctetString, - printable_string::PrintableString, - sequence::{iter::SequenceIter, Sequence}, - set_of::{SetOf, SetOfRef, SetOfRefIter}, - utc_time::UtcTime, - utf8_string::Utf8String, -}; - -#[cfg(feature = "oid")] -#[cfg_attr(docsrs, doc(cfg(feature = "oid")))] -pub use const_oid::ObjectIdentifier; diff --git a/der/src/asn1/any.rs b/der/src/asn1/any.rs deleted file mode 100644 index 33d9a2c1..00000000 --- a/der/src/asn1/any.rs +++ /dev/null @@ -1,239 +0,0 @@ -//! ASN.1 `ANY` type. - -use crate::{ - asn1::*, ByteSlice, Choice, Decodable, Decoder, Encodable, Encoder, Error, ErrorKind, Header, - Length, Result, Tag, -}; -use core::convert::{TryFrom, TryInto}; - -#[cfg(feature = "oid")] -use crate::asn1::ObjectIdentifier; - -/// ASN.1 `ANY`: represents any explicitly tagged ASN.1 value. -/// -/// Technically `ANY` hasn't been a recommended part of ASN.1 since the X.209 -/// revision from 1988. It was deprecated and replaced by Information Object -/// Classes in X.680 in 1994, and X.690 no longer refers to it whatsoever. -/// -/// Nevertheless, this crate defines an [`Any`] type as it remains a familiar -/// and useful concept which is still extensively used in things like -/// PKI-related RFCs. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct Any<'a> { - /// Tag representing the type of the encoded value. - tag: Tag, - - /// Encoded length of this [`Any`] value. - length: Length, - - /// Inner value encoded as bytes. - value: ByteSlice<'a>, -} - -impl<'a> Any<'a> { - /// Create a new [`Any`] from the provided [`Tag`] and byte slice. - pub fn new(tag: Tag, bytes: &'a [u8]) -> Result { - let value = ByteSlice::new(bytes).map_err(|_| ErrorKind::Length { tag })?; - - let length = if has_leading_zero_byte(tag) { - (value.len() + 1u8)? - } else { - value.len() - }; - - Ok(Self { tag, length, value }) - } - - /// Infallible creation of an [`Any`] from a [`ByteSlice`]. - pub(crate) fn from_tag_and_value(tag: Tag, value: ByteSlice<'a>) -> Self { - Self { - tag, - length: value.len(), - value, - } - } - - /// Get the tag for this [`Any`] type. - pub fn tag(self) -> Tag { - self.tag - } - - /// Get the [`Length`] of this [`Any`] type's value. - pub fn len(self) -> Length { - self.length - } - - /// Is the body of this [`Any`] type empty? - pub fn is_empty(self) -> bool { - self.value.is_empty() - } - - /// Is this value an ASN.1 NULL value? - pub fn is_null(self) -> bool { - Null::try_from(self).is_ok() - } - - /// Get the raw value for this [`Any`] type as a byte slice. - pub fn as_bytes(self) -> &'a [u8] { - self.value.as_bytes() - } - - /// Attempt to decode an ASN.1 `BIT STRING`. - pub fn bit_string(self) -> Result> { - self.try_into() - } - - /// Attempt to decode an ASN.1 `CONTEXT-SPECIFIC` field. - pub fn context_specific(self) -> Result> { - self.try_into() - } - - /// Attempt to decode an ASN.1 `GeneralizedTime`. - pub fn generalized_time(self) -> Result { - self.try_into() - } - - /// Attempt to decode an ASN.1 `IA5String`. - pub fn ia5_string(self) -> Result> { - self.try_into() - } - - /// Attempt to decode an ASN.1 `OCTET STRING`. - pub fn octet_string(self) -> Result> { - self.try_into() - } - - /// Attempt to decode an ASN.1 `OBJECT IDENTIFIER`. - #[cfg(feature = "oid")] - #[cfg_attr(docsrs, doc(cfg(feature = "oid")))] - pub fn oid(self) -> Result { - self.try_into() - } - - /// Attempt to decode an ASN.1 `OPTIONAL` value. - pub fn optional(self) -> Result> - where - T: Choice<'a> + TryFrom, - { - if T::can_decode(self.tag) { - T::try_from(self).map(Some) - } else { - Ok(None) - } - } - - /// Attempt to decode an ASN.1 `PrintableString`. - pub fn printable_string(self) -> Result> { - self.try_into() - } - - /// Attempt to decode this value an ASN.1 `SEQUENCE`, creating a new - /// nested [`Decoder`] and calling the provided argument with it. - pub fn sequence(self, f: F) -> Result - where - F: FnOnce(&mut Decoder<'a>) -> Result, - { - Sequence::try_from(self)?.decode_nested(f) - } - - /// Attempt to decode an ASN.1 `UTCTime`. - pub fn utc_time(self) -> Result { - self.try_into() - } - - /// Attempt to decode an ASN.1 `UTF8String`. - pub fn utf8_string(self) -> Result> { - self.try_into() - } -} - -impl<'a> Choice<'a> for Any<'a> { - fn can_decode(_: Tag) -> bool { - true - } -} - -impl<'a> Decodable<'a> for Any<'a> { - fn decode(decoder: &mut Decoder<'a>) -> Result> { - let header = Header::decode(decoder)?; - let tag = header.tag; - let mut value = decoder - .bytes(header.length) - .map_err(|_| decoder.error(ErrorKind::Length { tag }))?; - - if has_leading_zero_byte(tag) { - let (byte, rest) = value - .split_first() - .ok_or(ErrorKind::Truncated) - .map_err(|e| decoder.error(e))?; - - // The first octet of a BIT STRING encodes the number of unused bits. - // We presently constrain this to 0. - if *byte != 0 { - return Err(decoder.error(ErrorKind::Noncanonical { tag })); - } - - value = rest; - } - - Self::new(tag, value).map_err(|e| decoder.error(e.kind())) - } -} - -impl<'a> Encodable for Any<'a> { - fn encoded_len(&self) -> Result { - self.len().for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Header::new(self.tag, self.len())?.encode(encoder)?; - - if has_leading_zero_byte(self.tag) { - encoder.byte(0)?; - } - - encoder.bytes(self.as_bytes()) - } -} - -impl<'a> TryFrom<&'a [u8]> for Any<'a> { - type Error = Error; - - fn try_from(bytes: &'a [u8]) -> Result> { - Any::from_der(bytes) - } -} - -// Special handling for the leading `0` byte on [`BitString`] -impl<'a> TryFrom> for BitString<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - any.tag().assert_eq(Tag::BitString)?; - - Ok(BitString { - inner: any.value, - encoded_len: any.length, - }) - } -} - -// Special handling for the leading `0` byte on [`BitString`] -impl<'a> From> for Any<'a> { - fn from(bit_string: BitString<'a>) -> Any<'a> { - Any { - tag: Tag::BitString, - length: bit_string.encoded_len, - value: bit_string.inner, - } - } -} - -/// Does a value with this tag have a leading zero byte? -/// -/// This is mostly a hack for `BIT STRING`, and permits simple `From` -/// conversions from `BitString` into `Any`. -// TODO(tarcieri): better generalize this? or is there a better solution? -fn has_leading_zero_byte(tag: Tag) -> bool { - tag == Tag::BitString -} diff --git a/der/src/asn1/bit_string.rs b/der/src/asn1/bit_string.rs deleted file mode 100644 index 93b7b76b..00000000 --- a/der/src/asn1/bit_string.rs +++ /dev/null @@ -1,95 +0,0 @@ -//! ASN.1 `BIT STRING` support. - -use crate::{ByteSlice, Encodable, Encoder, ErrorKind, Header, Length, Result, Tag, Tagged}; - -/// ASN.1 `BIT STRING` type. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct BitString<'a> { - /// Inner value - pub(crate) inner: ByteSlice<'a>, - - /// Length after encoding (with leading `0`0 byte) - pub(crate) encoded_len: Length, -} - -impl<'a> BitString<'a> { - /// Create a new ASN.1 `BIT STRING` from a byte slice. - pub fn new(bytes: &'a [u8]) -> Result { - let inner = ByteSlice::new(bytes).map_err(|_| ErrorKind::Length { tag: Self::TAG })?; - let encoded_len = (inner.len() + 1u8).map_err(|_| ErrorKind::Length { tag: Self::TAG })?; - Ok(Self { inner, encoded_len }) - } - - /// Borrow the inner byte slice. - pub fn as_bytes(&self) -> &'a [u8] { - self.inner.as_bytes() - } - - /// Get the length of the inner byte slice (sans leading `0` byte). - pub fn len(&self) -> Length { - self.inner.len() - } - - /// Is the inner byte slice empty? - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl AsRef<[u8]> for BitString<'_> { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl<'a> From<&BitString<'a>> for BitString<'a> { - fn from(value: &BitString<'a>) -> BitString<'a> { - *value - } -} - -impl<'a> From> for &'a [u8] { - fn from(bit_string: BitString<'a>) -> &'a [u8] { - bit_string.as_bytes() - } -} - -impl<'a> Encodable for BitString<'a> { - fn encoded_len(&self) -> Result { - self.encoded_len.for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Header::new(Self::TAG, (Length::ONE + self.inner.len())?)?.encode(encoder)?; - encoder.byte(0)?; - encoder.bytes(self.as_bytes()) - } -} - -impl<'a> Tagged for BitString<'a> { - const TAG: Tag = Tag::BitString; -} - -#[cfg(test)] -mod tests { - use super::{BitString, Result, Tag}; - use crate::asn1::Any; - use core::convert::TryInto; - - /// Parse a `BitString` from an ASN.1 `Any` value to test decoding behaviors. - fn parse_bitstring_from_any(bytes: &[u8]) -> Result> { - Any::new(Tag::BitString, bytes)?.try_into() - } - - #[test] - fn decode_empty_bitstring() { - let bs = parse_bitstring_from_any(&[]).unwrap(); - assert_eq!(bs.as_ref(), &[]); - } - - #[test] - fn decode_non_empty_bitstring() { - let bs = parse_bitstring_from_any(&[1, 2, 3]).unwrap(); - assert_eq!(bs.as_ref(), &[1, 2, 3]); - } -} diff --git a/der/src/asn1/boolean.rs b/der/src/asn1/boolean.rs deleted file mode 100644 index 2885d168..00000000 --- a/der/src/asn1/boolean.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! ASN.1 `BOOLEAN` support. - -use crate::{asn1::Any, Encodable, Encoder, Error, Header, Length, Result, Tag, Tagged}; -use core::convert::TryFrom; - -/// Byte used to encode `true` in ASN.1 DER. From X.690 Section 11.1: -/// -/// > If the encoding represents the boolean value TRUE, its single contents -/// > octet shall have all eight bits set to one. -const TRUE_OCTET: u8 = 0b11111111; - -/// Byte used to encode `false` in ASN.1 DER. -const FALSE_OCTET: u8 = 0b00000000; - -impl TryFrom> for bool { - type Error = Error; - - fn try_from(any: Any<'_>) -> Result { - let tag = any.tag().assert_eq(Tag::Boolean)?; - - match any.as_bytes() { - [FALSE_OCTET] => Ok(false), - [TRUE_OCTET] => Ok(true), - _ => Err(tag.non_canonical_error()), - } - } -} - -impl Encodable for bool { - fn encoded_len(&self) -> Result { - Length::ONE.for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Header::new(Self::TAG, Length::ONE)?.encode(encoder)?; - let byte = if *self { TRUE_OCTET } else { FALSE_OCTET }; - encoder.byte(byte) - } -} - -impl Tagged for bool { - const TAG: Tag = Tag::Boolean; -} - -#[cfg(test)] -mod tests { - use crate::{Decodable, Encodable}; - - #[test] - fn decode() { - assert_eq!(true, bool::from_der(&[0x01, 0x01, 0xFF]).unwrap()); - assert_eq!(false, bool::from_der(&[0x01, 0x01, 0x00]).unwrap()); - } - - #[test] - fn encode() { - let mut buffer = [0u8; 3]; - assert_eq!( - &[0x01, 0x01, 0xFF], - true.encode_to_slice(&mut buffer).unwrap() - ); - assert_eq!( - &[0x01, 0x01, 0x00], - false.encode_to_slice(&mut buffer).unwrap() - ); - } - - #[test] - fn reject_non_canonical() { - assert!(bool::from_der(&[0x01, 0x01, 0x01]).is_err()); - } -} diff --git a/der/src/asn1/context_specific.rs b/der/src/asn1/context_specific.rs deleted file mode 100644 index f998cd3c..00000000 --- a/der/src/asn1/context_specific.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Context-specific field. - -use crate::{ - asn1::Any, Choice, Decodable, Encodable, Encoder, Error, ErrorKind, Header, Length, Result, - Tag, TagNumber, -}; -use core::convert::TryFrom; - -/// Context-specific field. -/// -/// This type encodes a field which is specific to a particular context, -/// and is identified by a [`TagNumber`]. -/// -/// Any context-specific field can be decoded/encoded with this type. -/// The intended use is to dynamically dispatch off of the context-specific -/// tag number when decoding, which allows support for extensions, which are -/// denoted in an ASN.1 schema using the `...` ellipsis extension marker. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct ContextSpecific<'a> { - /// Context-specific tag number sans the leading `0b10000000` class - /// identifier bit and `0b100000` constructed flag. - pub tag_number: TagNumber, - - /// Value of the field. - pub value: Any<'a>, -} - -impl<'a> Choice<'a> for ContextSpecific<'a> { - fn can_decode(tag: Tag) -> bool { - matches!(tag, Tag::ContextSpecific(_)) - } -} - -impl<'a> Encodable for ContextSpecific<'a> { - fn encoded_len(&self) -> Result { - self.value.encoded_len()?.for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - let tag = Tag::ContextSpecific(self.tag_number); - Header::new(tag, self.value.encoded_len()?)?.encode(encoder)?; - self.value.encode(encoder) - } -} - -impl<'a> From<&ContextSpecific<'a>> for ContextSpecific<'a> { - fn from(value: &ContextSpecific<'a>) -> ContextSpecific<'a> { - *value - } -} - -impl<'a> TryFrom> for ContextSpecific<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - match any.tag() { - Tag::ContextSpecific(tag_number) => Ok(Self { - tag_number, - value: Any::from_der(any.as_bytes())?, - }), - actual => Err(ErrorKind::UnexpectedTag { - expected: None, - actual, - } - .into()), - } - } -} - -#[cfg(test)] -mod tests { - use super::ContextSpecific; - use crate::{Decodable, Encodable, Tag}; - use hex_literal::hex; - - // Public key data from `pkcs8` crate's `ed25519-pkcs8-v2.der` - const EXAMPLE_BYTES: &[u8] = - &hex!("A123032100A3A7EAE3A8373830BC47E1167BC50E1DB551999651E0E2DC587623438EAC3F31"); - - #[test] - fn round_trip() { - let field = ContextSpecific::from_der(EXAMPLE_BYTES).unwrap(); - assert_eq!(field.tag_number.value(), 1); - assert_eq!(field.value.tag(), Tag::BitString); - assert_eq!(field.value.as_bytes(), &EXAMPLE_BYTES[5..]); - - let mut buf = [0u8; 128]; - let encoded = field.encode_to_slice(&mut buf).unwrap(); - assert_eq!(encoded, EXAMPLE_BYTES); - } -} diff --git a/der/src/asn1/generalized_time.rs b/der/src/asn1/generalized_time.rs deleted file mode 100644 index 2cd4d63b..00000000 --- a/der/src/asn1/generalized_time.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! ASN.1 `GeneralizedTime` support. - -use crate::{ - asn1::Any, - datetime::{self, DateTime}, - Encodable, Encoder, Error, Header, Length, Result, Tag, Tagged, -}; -use core::{convert::TryFrom, time::Duration}; - -#[cfg(feature = "std")] -use std::time::{SystemTime, UNIX_EPOCH}; - -/// Maximum duration since `UNIX_EPOCH` allowable as `GeneralizedTime`. -const MAX_UNIX_DURATION: Duration = Duration::from_secs(253_402_300_800); - -/// ASN.1 `GeneralizedTime` type. -/// -/// This type implements the validity requirements specified in -/// [RFC 5280 Section 4.1.2.5.2][1], namely: -/// -/// > For the purposes of this profile, GeneralizedTime values MUST be -/// > expressed in Greenwich Mean Time (Zulu) and MUST include seconds -/// > (i.e., times are `YYYYMMDDHHMMSSZ`), even where the number of seconds -/// > is zero. GeneralizedTime values MUST NOT include fractional seconds. -/// -/// [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct GeneralizedTime(Duration); - -impl GeneralizedTime { - /// Length of an RFC 5280-flavored ASN.1 DER-encoded [`GeneralizedTime`]. - pub const LENGTH: Length = Length::new(15); - - /// Create a new [`GeneralizedTime`] given a [`Duration`] since `UNIX_EPOCH` - /// (a.k.a. "Unix time") - pub fn new(unix_duration: Duration) -> Result { - if unix_duration < MAX_UNIX_DURATION { - Ok(Self(unix_duration)) - } else { - Err(Self::TAG.value_error()) - } - } - - /// Get the duration of this timestamp since `UNIX_EPOCH`. - pub fn unix_duration(&self) -> Duration { - self.0 - } - - /// Instantiate from [`SystemTime`]. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn from_system_time(time: SystemTime) -> Result { - time.duration_since(UNIX_EPOCH) - .map_err(|_| Self::TAG.value_error()) - .and_then(Self::new) - } - - /// Convert to [`SystemTime`]. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn to_system_time(&self) -> SystemTime { - UNIX_EPOCH + self.unix_duration() - } -} - -impl From<&GeneralizedTime> for GeneralizedTime { - fn from(value: &GeneralizedTime) -> GeneralizedTime { - *value - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl From for SystemTime { - fn from(utc_time: GeneralizedTime) -> SystemTime { - utc_time.to_system_time() - } -} - -impl TryFrom> for GeneralizedTime { - type Error = Error; - - fn try_from(any: Any<'_>) -> Result { - any.tag().assert_eq(Self::TAG)?; - - match *any.as_bytes() { - // RFC 5280 requires mandatory seconds and Z-normalized time zone - [y1, y2, y3, y4, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => { - let year = datetime::decode_decimal(Self::TAG, y1, y2)? * 100 - + datetime::decode_decimal(Self::TAG, y3, y4)?; - let month = datetime::decode_decimal(Self::TAG, mon1, mon2)?; - let day = datetime::decode_decimal(Self::TAG, day1, day2)?; - let hour = datetime::decode_decimal(Self::TAG, hour1, hour2)?; - let minute = datetime::decode_decimal(Self::TAG, min1, min2)?; - let second = datetime::decode_decimal(Self::TAG, sec1, sec2)?; - - DateTime::new(year, month, day, hour, minute, second) - .and_then(|dt| dt.unix_duration()) - .ok_or_else(|| Self::TAG.value_error()) - .and_then(Self::new) - } - _ => Err(Self::TAG.value_error()), - } - } -} - -impl Encodable for GeneralizedTime { - fn encoded_len(&self) -> Result { - Self::LENGTH.for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Header::new(Self::TAG, Self::LENGTH)?.encode(encoder)?; - - let datetime = - DateTime::from_unix_duration(self.0).ok_or_else(|| Self::TAG.value_error())?; - - let year_hi = datetime.year() / 100; - let year_lo = datetime.year() % 100; - - datetime::encode_decimal(encoder, Self::TAG, year_hi)?; - datetime::encode_decimal(encoder, Self::TAG, year_lo)?; - datetime::encode_decimal(encoder, Self::TAG, datetime.month())?; - datetime::encode_decimal(encoder, Self::TAG, datetime.day())?; - datetime::encode_decimal(encoder, Self::TAG, datetime.hour())?; - datetime::encode_decimal(encoder, Self::TAG, datetime.minute())?; - datetime::encode_decimal(encoder, Self::TAG, datetime.second())?; - encoder.byte(b'Z') - } -} - -impl Tagged for GeneralizedTime { - const TAG: Tag = Tag::GeneralizedTime; -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl<'a> TryFrom> for SystemTime { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result { - GeneralizedTime::try_from(any).map(|s| s.to_system_time()) - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl Encodable for SystemTime { - fn encoded_len(&self) -> Result { - GeneralizedTime::from_system_time(*self)?.encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - GeneralizedTime::from_system_time(*self)?.encode(encoder) - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl Tagged for SystemTime { - const TAG: Tag = Tag::GeneralizedTime; -} - -#[cfg(test)] -mod tests { - use super::GeneralizedTime; - use crate::{Decodable, Encodable, Encoder}; - use hex_literal::hex; - - #[test] - fn round_trip() { - let example_bytes = hex!("18 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a"); - let utc_time = GeneralizedTime::from_der(&example_bytes).unwrap(); - assert_eq!(utc_time.unix_duration().as_secs(), 673573540); - - let mut buf = [0u8; 128]; - let mut encoder = Encoder::new(&mut buf); - utc_time.encode(&mut encoder).unwrap(); - assert_eq!(example_bytes, encoder.finish().unwrap()); - } -} diff --git a/der/src/asn1/ia5_string.rs b/der/src/asn1/ia5_string.rs deleted file mode 100644 index da8f3336..00000000 --- a/der/src/asn1/ia5_string.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! ASN.1 `IA5String` support. - -use crate::{ - asn1::Any, str_slice::StrSlice, Encodable, Encoder, Error, Length, Result, Tag, Tagged, -}; -use core::{convert::TryFrom, fmt, str}; - -/// ASN.1 `IA5String` type. -/// -/// Supports the [International Alphabet No. 5 (IA5)] character encoding, i.e. -/// the lower 128 characters of the ASCII alphabet. (Note: IA5 is now -/// technically known as the International Reference Alphabet or IRA as -/// specified in the ITU-T's T.50 recommendation). -/// -/// For UTF-8, use [`Utf8String`][`crate::asn1::Utf8String`]. -/// -/// [International Alphabet No. 5 (IA5)]: https://en.wikipedia.org/wiki/T.50_%28standard%29 -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] -pub struct Ia5String<'a> { - /// Inner value - inner: StrSlice<'a>, -} - -impl<'a> Ia5String<'a> { - /// Create a new `IA5String`. - pub fn new(input: &'a T) -> Result - where - T: AsRef<[u8]> + ?Sized, - { - let input = input.as_ref(); - - // Validate all characters are within IA5String's allowed set - if input.iter().any(|&c| c > 0x7F) { - return Err(Self::TAG.value_error()); - } - - StrSlice::from_bytes(input) - .map(|inner| Self { inner }) - .map_err(|_| Self::TAG.value_error()) - } - - /// Borrow the string as a `str`. - pub fn as_str(&self) -> &'a str { - self.inner.as_str() - } - - /// Borrow the string as bytes. - pub fn as_bytes(&self) -> &'a [u8] { - self.inner.as_bytes() - } - - /// Get the length of the inner byte slice. - pub fn len(&self) -> Length { - self.inner.len() - } - - /// Is the inner string empty? - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl AsRef for Ia5String<'_> { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl AsRef<[u8]> for Ia5String<'_> { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl<'a> From<&Ia5String<'a>> for Ia5String<'a> { - fn from(value: &Ia5String<'a>) -> Ia5String<'a> { - *value - } -} - -impl<'a> TryFrom> for Ia5String<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - any.tag().assert_eq(Tag::Ia5String)?; - Self::new(any.as_bytes()) - } -} - -impl<'a> From> for Any<'a> { - fn from(printable_string: Ia5String<'a>) -> Any<'a> { - Any::from_tag_and_value(Tag::Ia5String, printable_string.inner.into()) - } -} - -impl<'a> From> for &'a [u8] { - fn from(printable_string: Ia5String<'a>) -> &'a [u8] { - printable_string.as_bytes() - } -} - -impl<'a> Encodable for Ia5String<'a> { - fn encoded_len(&self) -> Result { - Any::from(*self).encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Any::from(*self).encode(encoder) - } -} - -impl<'a> Tagged for Ia5String<'a> { - const TAG: Tag = Tag::Ia5String; -} - -impl<'a> fmt::Display for Ia5String<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -impl<'a> fmt::Debug for Ia5String<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Ia5String({:?})", self.as_str()) - } -} - -#[cfg(test)] -mod tests { - use super::Ia5String; - use crate::Decodable; - use hex_literal::hex; - - #[test] - fn parse_bytes() { - let example_bytes = hex!("16 0d 74 65 73 74 31 40 72 73 61 2e 63 6f 6d"); - let printable_string = Ia5String::from_der(&example_bytes).unwrap(); - assert_eq!(printable_string.as_str(), "test1@rsa.com"); - } -} diff --git a/der/src/asn1/integer.rs b/der/src/asn1/integer.rs deleted file mode 100644 index d5969256..00000000 --- a/der/src/asn1/integer.rs +++ /dev/null @@ -1,227 +0,0 @@ -//! ASN.1 `INTEGER` support. - -pub(super) mod bigint; -mod int; -mod uint; - -use crate::{asn1::Any, Encodable, Encoder, Error, Length, Result, Tag, Tagged}; -use core::convert::TryFrom; - -macro_rules! impl_int_encoding { - ($($int:ty => $uint:ty),+) => { - $( - impl TryFrom> for $int { - type Error = Error; - - fn try_from(any: Any<'_>) -> Result { - let result = if is_highest_bit_set(any.as_bytes()) { - <$uint>::from_be_bytes(int::decode_array(any)?) as $int - } else { - Self::from_be_bytes(uint::decode_array(any)?) - }; - - // Ensure we compute the same encoded length as the original any value - if any.encoded_len()? != result.encoded_len()? { - return Err(Self::TAG.non_canonical_error()); - } - - Ok(result) - } - } - - impl Encodable for $int { - fn encoded_len(&self) -> Result { - if *self < 0 { - int::encoded_len(&(*self as $uint).to_be_bytes())?.for_tlv() - } else { - uint::encoded_len(&self.to_be_bytes())?.for_tlv() - } - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - if *self < 0 { - int::encode(encoder, &(*self as $uint).to_be_bytes()) - } else { - uint::encode(encoder, &self.to_be_bytes()) - } - } - } - - impl Tagged for $int { - const TAG: Tag = Tag::Integer; - } - )+ - }; -} - -macro_rules! impl_uint_encoding { - ($($uint:ty),+) => { - $( - impl TryFrom> for $uint { - type Error = Error; - - fn try_from(any: Any<'_>) -> Result { - let result = Self::from_be_bytes(uint::decode_array(any)?); - - // Ensure we compute the same encoded length as the original any value - if any.encoded_len()? != result.encoded_len()? { - return Err(Self::TAG.non_canonical_error()); - } - - Ok(result) - } - } - - impl Encodable for $uint { - fn encoded_len(&self) -> Result { - uint::encoded_len(&self.to_be_bytes())?.for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - uint::encode(encoder, &self.to_be_bytes()) - } - } - - impl Tagged for $uint { - const TAG: Tag = Tag::Integer; - } - )+ - }; -} - -impl_int_encoding!(i8 => u8, i16 => u16, i32 => u32, i64 => u64, i128 => u128); -impl_uint_encoding!(u8, u16, u32, u64, u128); - -/// Is the highest bit of the first byte in the slice 1? (if present) -#[inline] -fn is_highest_bit_set(bytes: &[u8]) -> bool { - bytes - .get(0) - .map(|byte| byte & 0b10000000 != 0) - .unwrap_or(false) -} - -#[cfg(test)] -pub(crate) mod tests { - use crate::{Decodable, Encodable}; - - // Vectors from Section 5.7 of: - // https://luca.ntop.org/Teaching/Appunti/asn1.html - pub(crate) const I0_BYTES: &[u8] = &[0x02, 0x01, 0x00]; - pub(crate) const I127_BYTES: &[u8] = &[0x02, 0x01, 0x7F]; - pub(crate) const I128_BYTES: &[u8] = &[0x02, 0x02, 0x00, 0x80]; - pub(crate) const I256_BYTES: &[u8] = &[0x02, 0x02, 0x01, 0x00]; - pub(crate) const INEG128_BYTES: &[u8] = &[0x02, 0x01, 0x80]; - pub(crate) const INEG129_BYTES: &[u8] = &[0x02, 0x02, 0xFF, 0x7F]; - - // Additional vectors - pub(crate) const I255_BYTES: &[u8] = &[0x02, 0x02, 0x00, 0xFF]; - pub(crate) const I32767_BYTES: &[u8] = &[0x02, 0x02, 0x7F, 0xFF]; - pub(crate) const I65535_BYTES: &[u8] = &[0x02, 0x03, 0x00, 0xFF, 0xFF]; - pub(crate) const INEG32768_BYTES: &[u8] = &[0x02, 0x02, 0x80, 0x00]; - - #[test] - fn decode_i8() { - assert_eq!(0, i8::from_der(I0_BYTES).unwrap()); - assert_eq!(127, i8::from_der(I127_BYTES).unwrap()); - assert_eq!(-128, i8::from_der(INEG128_BYTES).unwrap()); - } - - #[test] - fn decode_i16() { - assert_eq!(0, i16::from_der(I0_BYTES).unwrap()); - assert_eq!(127, i16::from_der(I127_BYTES).unwrap()); - assert_eq!(128, i16::from_der(I128_BYTES).unwrap()); - assert_eq!(255, i16::from_der(I255_BYTES).unwrap()); - assert_eq!(256, i16::from_der(I256_BYTES).unwrap()); - assert_eq!(32767, i16::from_der(I32767_BYTES).unwrap()); - assert_eq!(-128, i16::from_der(INEG128_BYTES).unwrap()); - assert_eq!(-129, i16::from_der(INEG129_BYTES).unwrap()); - assert_eq!(-32768, i16::from_der(INEG32768_BYTES).unwrap()); - } - - #[test] - fn decode_u8() { - assert_eq!(0, u8::from_der(I0_BYTES).unwrap()); - assert_eq!(127, u8::from_der(I127_BYTES).unwrap()); - assert_eq!(255, u8::from_der(I255_BYTES).unwrap()); - } - - #[test] - fn decode_u16() { - assert_eq!(0, u16::from_der(I0_BYTES).unwrap()); - assert_eq!(127, u16::from_der(I127_BYTES).unwrap()); - assert_eq!(255, u16::from_der(I255_BYTES).unwrap()); - assert_eq!(256, u16::from_der(I256_BYTES).unwrap()); - assert_eq!(32767, u16::from_der(I32767_BYTES).unwrap()); - assert_eq!(65535, u16::from_der(I65535_BYTES).unwrap()); - } - - #[test] - fn encode_i8() { - let mut buffer = [0u8; 3]; - - assert_eq!(I0_BYTES, 0i8.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I127_BYTES, 127i8.encode_to_slice(&mut buffer).unwrap()); - - assert_eq!( - INEG128_BYTES, - (-128i8).encode_to_slice(&mut buffer).unwrap() - ); - } - - #[test] - fn encode_i16() { - let mut buffer = [0u8; 4]; - assert_eq!(I0_BYTES, 0i16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I127_BYTES, 127i16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I128_BYTES, 128i16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I255_BYTES, 255i16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I256_BYTES, 256i16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I32767_BYTES, 32767i16.encode_to_slice(&mut buffer).unwrap()); - - assert_eq!( - INEG128_BYTES, - (-128i16).encode_to_slice(&mut buffer).unwrap() - ); - - assert_eq!( - INEG129_BYTES, - (-129i16).encode_to_slice(&mut buffer).unwrap() - ); - - assert_eq!( - INEG32768_BYTES, - (-32768i16).encode_to_slice(&mut buffer).unwrap() - ); - } - - #[test] - fn encode_u8() { - let mut buffer = [0u8; 4]; - assert_eq!(I0_BYTES, 0u8.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I127_BYTES, 127u8.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I255_BYTES, 255u8.encode_to_slice(&mut buffer).unwrap()); - } - - #[test] - fn encode_u16() { - let mut buffer = [0u8; 5]; - assert_eq!(I0_BYTES, 0u16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I127_BYTES, 127u16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I128_BYTES, 128u16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I255_BYTES, 255u16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I256_BYTES, 256u16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I32767_BYTES, 32767u16.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(I65535_BYTES, 65535u16.encode_to_slice(&mut buffer).unwrap()); - } - - /// Integers must be encoded with a minimum number of octets - #[test] - fn reject_non_canonical() { - assert!(i8::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err()); - assert!(i16::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err()); - assert!(u8::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err()); - assert!(u16::from_der(&[0x02, 0x02, 0x00, 0x00]).is_err()); - } -} diff --git a/der/src/asn1/integer/bigint.rs b/der/src/asn1/integer/bigint.rs deleted file mode 100644 index 482b7d0b..00000000 --- a/der/src/asn1/integer/bigint.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! "Big" ASN.1 `INTEGER` types. - -use super::uint; -use crate::{ - asn1::Any, ByteSlice, Encodable, Encoder, Error, ErrorKind, Header, Length, Result, Tag, Tagged, -}; -use core::convert::TryFrom; - -#[cfg(feature = "bigint")] -use { - core::convert::TryInto, - crypto_bigint::{generic_array::GenericArray, ArrayEncoding, UInt}, -}; - -/// "Big" unsigned ASN.1 `INTEGER` type. -/// -/// Provides direct access to the underlying big endian bytes which comprise an -/// unsigned integer value. -/// -/// Intended for use cases like very large integers that are used in -/// cryptographic applications (e.g. keys, signatures). -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)] -pub struct UIntBytes<'a> { - /// Inner value - inner: ByteSlice<'a>, -} - -impl<'a> UIntBytes<'a> { - /// Create a new [`UIntBytes`] from a byte slice. - pub fn new(bytes: &'a [u8]) -> Result { - let inner = ByteSlice::new(uint::strip_leading_zeroes(bytes)) - .map_err(|_| ErrorKind::Length { tag: Self::TAG })?; - - Ok(Self { inner }) - } - - /// Borrow the inner byte slice which contains the least significant bytes - /// of a big endian integer value with all leading zeros stripped. - pub fn as_bytes(&self) -> &'a [u8] { - self.inner.as_bytes() - } - - /// Get the length of this [`UIntBytes`] in bytes. - pub fn len(&self) -> Length { - self.inner.len() - } - - /// Is the inner byte slice empty? - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } - - /// Get the length of the inner integer value when encoded. - fn inner_len(self) -> Result { - uint::encoded_len(self.inner.as_bytes()) - } -} - -impl<'a> From<&UIntBytes<'a>> for UIntBytes<'a> { - fn from(value: &UIntBytes<'a>) -> UIntBytes<'a> { - *value - } -} - -impl<'a> TryFrom> for UIntBytes<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - Self::new(uint::decode_slice(any)?) - } -} - -impl<'a> Encodable for UIntBytes<'a> { - fn encoded_len(&self) -> Result { - self.inner_len()?.for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Header::new(Self::TAG, self.inner_len()?)?.encode(encoder)?; - - // Add leading `0x00` byte if required - if self.inner_len()? > self.len() { - encoder.byte(0)?; - } - - encoder.bytes(self.as_bytes()) - } -} - -impl<'a> Tagged for UIntBytes<'a> { - const TAG: Tag = Tag::Integer; -} - -#[cfg(feature = "bigint")] -#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))] -impl<'a, const LIMBS: usize> TryFrom> for UInt -where - UInt: ArrayEncoding, -{ - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - UIntBytes::try_from(any)?.try_into() - } -} - -#[cfg(feature = "bigint")] -#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))] -impl<'a, const LIMBS: usize> TryFrom> for UInt -where - UInt: ArrayEncoding, -{ - type Error = Error; - - fn try_from(bytes: UIntBytes<'a>) -> Result> { - let mut array = GenericArray::default(); - let offset = array.len().saturating_sub(bytes.len().try_into()?); - array[offset..].copy_from_slice(bytes.as_bytes()); - Ok(UInt::from_be_byte_array(array)) - } -} - -#[cfg(feature = "bigint")] -#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))] -impl<'a, const LIMBS: usize> Encodable for UInt -where - UInt: ArrayEncoding, -{ - fn encoded_len(&self) -> Result { - // TODO(tarcieri): more efficient length calculation - let array = self.to_be_byte_array(); - UIntBytes::new(&array)?.encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - let array = self.to_be_byte_array(); - UIntBytes::new(&array)?.encode(encoder) - } -} - -#[cfg(feature = "bigint")] -#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))] -impl<'a, const LIMBS: usize> Tagged for UInt -where - UInt: ArrayEncoding, -{ - const TAG: Tag = Tag::Integer; -} - -#[cfg(test)] -mod tests { - use super::UIntBytes; - use crate::{ - asn1::{integer::tests::*, Any}, - Decodable, Encodable, Encoder, ErrorKind, Tag, - }; - use core::convert::TryFrom; - - #[test] - fn decode_uint_bytes() { - assert_eq!(&[0], UIntBytes::from_der(I0_BYTES).unwrap().as_bytes()); - assert_eq!(&[127], UIntBytes::from_der(I127_BYTES).unwrap().as_bytes()); - assert_eq!(&[128], UIntBytes::from_der(I128_BYTES).unwrap().as_bytes()); - assert_eq!(&[255], UIntBytes::from_der(I255_BYTES).unwrap().as_bytes()); - - assert_eq!( - &[0x01, 0x00], - UIntBytes::from_der(I256_BYTES).unwrap().as_bytes() - ); - - assert_eq!( - &[0x7F, 0xFF], - UIntBytes::from_der(I32767_BYTES).unwrap().as_bytes() - ); - } - - #[test] - fn encode_uint_bytes() { - for &example in &[ - I0_BYTES, - I127_BYTES, - I128_BYTES, - I255_BYTES, - I256_BYTES, - I32767_BYTES, - ] { - let uint = UIntBytes::from_der(example).unwrap(); - - let mut buf = [0u8; 128]; - let mut encoder = Encoder::new(&mut buf); - uint.encode(&mut encoder).unwrap(); - - let result = encoder.finish().unwrap(); - assert_eq!(example, result); - } - } - - #[test] - fn reject_oversize_without_extra_zero() { - let err = UIntBytes::try_from(Any::new(Tag::Integer, &[0x81]).unwrap()) - .err() - .unwrap(); - - assert_eq!(err.kind(), ErrorKind::Value { tag: Tag::Integer }); - } -} diff --git a/der/src/asn1/integer/int.rs b/der/src/asn1/integer/int.rs deleted file mode 100644 index f8c19844..00000000 --- a/der/src/asn1/integer/int.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Support for encoding negative integers - -use super::is_highest_bit_set; -use crate::{asn1::Any, Encodable, Encoder, Header, Length, Result, Tag}; -use core::convert::TryFrom; - -/// Decode an unsigned integer of the specified size. -/// -/// Returns a byte array of the requested size containing a big endian integer. -pub(super) fn decode_array(any: Any<'_>) -> Result<[u8; N]> { - any.tag().assert_eq(Tag::Integer)?; - let mut output = [0xFFu8; N]; - let offset = N.saturating_sub(any.as_bytes().len()); - output[offset..].copy_from_slice(any.as_bytes()); - Ok(output) -} - -/// Encode the given big endian bytes representing an integer as ASN.1 DER. -pub(super) fn encode(encoder: &mut Encoder<'_>, bytes: &[u8]) -> Result<()> { - let bytes = strip_leading_ones(&bytes); - let len = Length::try_from(bytes.len())?; - Header::new(Tag::Integer, len)?.encode(encoder)?; - encoder.bytes(bytes) -} - -/// Get the encoded length for the given unsigned integer serialized as bytes. -#[inline] -pub(super) fn encoded_len(bytes: &[u8]) -> Result { - Length::try_from(strip_leading_ones(&bytes).len()) -} - -/// Strip the leading all-ones bytes from the given byte slice. -fn strip_leading_ones(mut bytes: &[u8]) -> &[u8] { - while let Some((byte, rest)) = bytes.split_first() { - if *byte == 0xFF && is_highest_bit_set(rest) { - bytes = rest; - continue; - } - - break; - } - - bytes -} diff --git a/der/src/asn1/integer/uint.rs b/der/src/asn1/integer/uint.rs deleted file mode 100644 index 743087ed..00000000 --- a/der/src/asn1/integer/uint.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! Unsigned integer decoders/encoders. - -use crate::{asn1::Any, Encodable, Encoder, Header, Length, Result, Tag}; -use core::convert::TryFrom; - -/// Decode an unsigned integer into a big endian byte slice with all leading -/// zeroes removed. -/// -/// Returns a byte array of the requested size containing a big endian integer. -pub(super) fn decode_slice(any: Any<'_>) -> Result<&[u8]> { - let tag = any.tag().assert_eq(Tag::Integer)?; - let bytes = any.as_bytes(); - - // The `INTEGER` type always encodes a signed value, so for unsigned - // values the leading `0x00` byte may need to be removed. - // - // We also disallow a leading byte which would overflow a signed ASN.1 - // integer (since we're decoding an unsigned integer). - // We expect all such cases to have a leading `0x00` byte. - match bytes { - [] => Err(tag.non_canonical_error()), - [0] => Ok(bytes), - [0, byte, ..] if *byte < 0x80 => Err(tag.non_canonical_error()), - [0, rest @ ..] => Ok(&rest), - [byte, ..] if *byte >= 0x80 => Err(tag.value_error()), - _ => Ok(bytes), - } -} - -/// Decode an unsigned integer into a byte array of the requested size -/// containing a big endian integer. -pub(super) fn decode_array(any: Any<'_>) -> Result<[u8; N]> { - let input = decode_slice(any)?; - - // Input has leading zeroes removed, so we need to add them back - let mut output = [0u8; N]; - output[N.saturating_sub(input.len())..].copy_from_slice(input); - Ok(output) -} - -/// Encode the given big endian bytes representing an integer as ASN.1 DER. -pub(super) fn encode(encoder: &mut Encoder<'_>, bytes: &[u8]) -> Result<()> { - let bytes = strip_leading_zeroes(&bytes); - let leading_zero = needs_leading_zero(bytes); - let len = (Length::try_from(bytes.len())? + leading_zero as u8)?; - Header::new(Tag::Integer, len)?.encode(encoder)?; - - if leading_zero { - encoder.byte(0)?; - } - - encoder.bytes(bytes) -} - -/// Get the encoded length for the given unsigned integer serialized as bytes. -#[inline] -pub(super) fn encoded_len(bytes: &[u8]) -> Result { - let bytes = strip_leading_zeroes(&bytes); - Length::try_from(bytes.len())? + needs_leading_zero(bytes) as u8 -} - -/// Strip the leading zeroes from the given byte slice -pub(super) fn strip_leading_zeroes(mut bytes: &[u8]) -> &[u8] { - while let Some((byte, rest)) = bytes.split_first() { - if *byte == 0 && !rest.is_empty() { - bytes = rest; - } else { - break; - } - } - - bytes -} - -/// Does the given integer need a leading zero? -fn needs_leading_zero(bytes: &[u8]) -> bool { - matches!(bytes.get(0), Some(byte) if *byte >= 0x80) -} diff --git a/der/src/asn1/null.rs b/der/src/asn1/null.rs deleted file mode 100644 index 5a522b56..00000000 --- a/der/src/asn1/null.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! ASN.1 `NULL` support. - -use crate::{ - asn1::Any, ByteSlice, Encodable, Encoder, Error, ErrorKind, Length, Result, Tag, Tagged, -}; -use core::convert::TryFrom; - -/// ASN.1 `NULL` type. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct Null; - -impl TryFrom> for Null { - type Error = Error; - - fn try_from(any: Any<'_>) -> Result { - let tag = any.tag().assert_eq(Tag::Null)?; - - if any.is_empty() { - Ok(Null) - } else { - Err(ErrorKind::Length { tag }.into()) - } - } -} - -impl<'a> From for Any<'a> { - fn from(_: Null) -> Any<'a> { - Any::from_tag_and_value(Tag::Null, ByteSlice::default()) - } -} - -impl Encodable for Null { - fn encoded_len(&self) -> Result { - Any::from(*self).encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Any::from(*self).encode(encoder) - } -} - -impl Tagged for Null { - const TAG: Tag = Tag::Integer; -} - -impl TryFrom> for () { - type Error = Error; - - fn try_from(any: Any<'_>) -> Result<()> { - let tag = any.tag().assert_eq(Tag::Null)?; - - if any.is_empty() { - Ok(()) - } else { - Err(ErrorKind::Length { tag }.into()) - } - } -} - -impl<'a> From<()> for Any<'a> { - fn from(_: ()) -> Any<'a> { - Null.into() - } -} - -impl Encodable for () { - fn encoded_len(&self) -> Result { - Any::from(()).encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Any::from(()).encode(encoder) - } -} - -impl Tagged for () { - const TAG: Tag = Tag::Null; -} - -#[cfg(test)] -mod tests { - use super::Null; - use crate::{Decodable, Encodable}; - - #[test] - fn decode() { - assert!(Null::from_der(&[0x05, 0x00]).is_ok()); - } - - #[test] - fn encode() { - let mut buffer = [0u8; 2]; - assert_eq!(&[0x05, 0x00], Null.encode_to_slice(&mut buffer).unwrap()); - assert_eq!(&[0x05, 0x00], ().encode_to_slice(&mut buffer).unwrap()); - } - - #[test] - fn reject_non_canonical() { - assert!(Null::from_der(&[0x05, 0x81, 0x00]).is_err()); - } -} diff --git a/der/src/asn1/octet_string.rs b/der/src/asn1/octet_string.rs deleted file mode 100644 index 8a50ae7f..00000000 --- a/der/src/asn1/octet_string.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! ASN.1 `OCTET STRING` support. - -use crate::{ - asn1::Any, ByteSlice, Encodable, Encoder, Error, ErrorKind, Length, Result, Tag, Tagged, -}; -use core::convert::TryFrom; - -/// ASN.1 `OCTET STRING` type. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct OctetString<'a> { - /// Inner value - inner: ByteSlice<'a>, -} - -impl<'a> OctetString<'a> { - /// Create a new ASN.1 `OCTET STRING` from a byte slice. - pub fn new(slice: &'a [u8]) -> Result { - ByteSlice::new(slice) - .map(|inner| Self { inner }) - .map_err(|_| ErrorKind::Length { tag: Self::TAG }.into()) - } - - /// Borrow the inner byte slice. - pub fn as_bytes(&self) -> &'a [u8] { - self.inner.as_bytes() - } - - /// Get the length of the inner byte slice. - pub fn len(&self) -> Length { - self.inner.len() - } - - /// Is the inner byte slice empty? - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl AsRef<[u8]> for OctetString<'_> { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl<'a> From<&OctetString<'a>> for OctetString<'a> { - fn from(value: &OctetString<'a>) -> OctetString<'a> { - *value - } -} - -impl<'a> TryFrom> for OctetString<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - any.tag().assert_eq(Tag::OctetString)?; - Self::new(any.as_bytes()) - } -} - -impl<'a> From> for Any<'a> { - fn from(octet_string: OctetString<'a>) -> Any<'a> { - Any::from_tag_and_value(Tag::OctetString, octet_string.inner) - } -} - -impl<'a> From> for &'a [u8] { - fn from(octet_string: OctetString<'a>) -> &'a [u8] { - octet_string.as_bytes() - } -} - -impl<'a> Encodable for OctetString<'a> { - fn encoded_len(&self) -> Result { - Any::from(*self).encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Any::from(*self).encode(encoder) - } -} - -impl<'a> Tagged for OctetString<'a> { - const TAG: Tag = Tag::OctetString; -} diff --git a/der/src/asn1/oid.rs b/der/src/asn1/oid.rs deleted file mode 100644 index 06871685..00000000 --- a/der/src/asn1/oid.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! ASN.1 `OBJECT IDENTIFIER` - -use crate::{asn1::Any, Encodable, Encoder, Error, Length, Result, Tag, Tagged}; -use const_oid::ObjectIdentifier; -use core::convert::{TryFrom, TryInto}; - -impl TryFrom> for ObjectIdentifier { - type Error = Error; - - fn try_from(any: Any<'_>) -> Result { - any.tag().assert_eq(Tag::ObjectIdentifier)?; - Ok(ObjectIdentifier::from_bytes(any.as_bytes())?) - } -} - -impl<'a> From<&'a ObjectIdentifier> for Any<'a> { - fn from(oid: &'a ObjectIdentifier) -> Any<'a> { - // Note: ensuring an infallible conversion is possible relies on the - // invariant that `const_oid::MAX_LEN <= Length::max()`. - // - // The `length()` test below ensures this is the case. - let value = oid - .as_bytes() - .try_into() - .expect("OID length invariant violated"); - - Any::from_tag_and_value(Tag::ObjectIdentifier, value) - } -} - -impl Encodable for ObjectIdentifier { - fn encoded_len(&self) -> Result { - Any::from(self).encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Any::from(self).encode(encoder) - } -} - -impl<'a> Tagged for ObjectIdentifier { - const TAG: Tag = Tag::ObjectIdentifier; -} - -#[cfg(test)] -mod tests { - use super::ObjectIdentifier; - use crate::{Decodable, Encodable, Length}; - use core::convert::TryInto; - - const EXAMPLE_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549"); - const EXAMPLE_OID_BYTES: &[u8; 8] = &[0x06, 0x06, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d]; - - #[test] - fn decode() { - let oid = ObjectIdentifier::from_der(EXAMPLE_OID_BYTES).unwrap(); - assert_eq!(EXAMPLE_OID, oid); - } - - #[test] - fn encode() { - let mut buffer = [0u8; 8]; - assert_eq!( - EXAMPLE_OID_BYTES, - EXAMPLE_OID.encode_to_slice(&mut buffer).unwrap() - ); - } - - #[test] - fn length() { - // Ensure an infallible `From` conversion to `Any` will never panic - assert!(ObjectIdentifier::MAX_LENGTH <= Length::MAX.try_into().unwrap()); - } -} diff --git a/der/src/asn1/optional.rs b/der/src/asn1/optional.rs deleted file mode 100644 index f6853138..00000000 --- a/der/src/asn1/optional.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! ASN.1 `OPTIONAL` as mapped to Rust's `Option` type - -use crate::{Choice, Decodable, Decoder, Encodable, Encoder, Length, Result, Tag}; -use core::convert::TryFrom; - -impl<'a, T> Decodable<'a> for Option -where - T: Choice<'a>, // NOTE: all `Decodable + Tagged` types receive a blanket `Choice` impl -{ - fn decode(decoder: &mut Decoder<'a>) -> Result> { - if let Some(byte) = decoder.peek() { - if T::can_decode(Tag::try_from(byte)?) { - return T::decode(decoder).map(Some); - } - } - - Ok(None) - } -} - -impl Encodable for Option -where - T: Encodable, -{ - fn encoded_len(&self) -> Result { - if let Some(encodable) = self { - encodable.encoded_len() - } else { - Ok(0u8.into()) - } - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - if let Some(encodable) = self { - encodable.encode(encoder) - } else { - Ok(()) - } - } -} diff --git a/der/src/asn1/printable_string.rs b/der/src/asn1/printable_string.rs deleted file mode 100644 index 8b1ecdf3..00000000 --- a/der/src/asn1/printable_string.rs +++ /dev/null @@ -1,175 +0,0 @@ -//! ASN.1 `PrintableString` support. - -use crate::{ - asn1::Any, str_slice::StrSlice, Encodable, Encoder, Error, Length, Result, Tag, Tagged, -}; -use core::{convert::TryFrom, fmt, str}; - -/// ASN.1 `PrintableString` type. -/// -/// Supports a subset the ASCII character set (desribed below). -/// -/// For UTF-8, use [`Utf8String`][`crate::asn1::Utf8String`] instead. For the -/// full ASCII character set, use [`Ia5String`][`crate::asn1::Ia5String`]. -/// -/// # Supported characters -/// -/// The following ASCII characters/ranges are supported: -/// -/// - `A..Z` -/// - `a..z` -/// - `0..9` -/// - "` `" (i.e. space) -/// - `\` -/// - `(` -/// - `)` -/// - `+` -/// - `,` -/// - `-` -/// - `.` -/// - `/` -/// - `:` -/// - `=` -/// - `?` -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] -pub struct PrintableString<'a> { - /// Inner value - inner: StrSlice<'a>, -} - -impl<'a> PrintableString<'a> { - /// Create a new ASN.1 `PrintableString`. - pub fn new(input: &'a T) -> Result - where - T: AsRef<[u8]> + ?Sized, - { - let input = input.as_ref(); - - // Validate all characters are within PrintedString's allowed set - for &c in input.iter() { - match c { - b'A'..=b'Z' - | b'a'..=b'z' - | b'0'..=b'9' - | b' ' - | b'\'' - | b'(' - | b')' - | b'+' - | b',' - | b'-' - | b'.' - | b'/' - | b':' - | b'=' - | b'?' => (), - _ => return Err(Self::TAG.value_error()), - } - } - - StrSlice::from_bytes(input) - .map(|inner| Self { inner }) - .map_err(|_| Self::TAG.value_error()) - } - - /// Borrow the string as a `str`. - pub fn as_str(&self) -> &'a str { - self.inner.as_str() - } - - /// Borrow the string as bytes. - pub fn as_bytes(&self) -> &'a [u8] { - self.inner.as_bytes() - } - - /// Get the length of the inner byte slice. - pub fn len(&self) -> Length { - self.inner.len() - } - - /// Is the inner string empty? - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl AsRef for PrintableString<'_> { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl AsRef<[u8]> for PrintableString<'_> { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl<'a> From<&PrintableString<'a>> for PrintableString<'a> { - fn from(value: &PrintableString<'a>) -> PrintableString<'a> { - *value - } -} - -impl<'a> TryFrom> for PrintableString<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - any.tag().assert_eq(Tag::PrintableString)?; - Self::new(any.as_bytes()) - } -} - -impl<'a> From> for Any<'a> { - fn from(printable_string: PrintableString<'a>) -> Any<'a> { - Any::from_tag_and_value(Tag::PrintableString, printable_string.inner.into()) - } -} - -impl<'a> From> for &'a [u8] { - fn from(printable_string: PrintableString<'a>) -> &'a [u8] { - printable_string.as_bytes() - } -} - -impl<'a> Encodable for PrintableString<'a> { - fn encoded_len(&self) -> Result { - Any::from(*self).encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Any::from(*self).encode(encoder) - } -} - -impl<'a> Tagged for PrintableString<'a> { - const TAG: Tag = Tag::PrintableString; -} - -impl<'a> fmt::Display for PrintableString<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -impl<'a> fmt::Debug for PrintableString<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "PrintableString({:?})", self.as_str()) - } -} - -#[cfg(test)] -mod tests { - use super::PrintableString; - use crate::Decodable; - - #[test] - fn parse_bytes() { - let example_bytes = &[ - 0x13, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x20, 0x55, 0x73, 0x65, 0x72, 0x20, 0x31, - ]; - - let printable_string = PrintableString::from_der(example_bytes).unwrap(); - assert_eq!(printable_string.as_str(), "Test User 1"); - } -} diff --git a/der/src/asn1/sequence.rs b/der/src/asn1/sequence.rs deleted file mode 100644 index ae11f290..00000000 --- a/der/src/asn1/sequence.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! ASN.1 `SEQUENCE` support. - -pub(super) mod iter; - -use self::iter::SequenceIter; -use crate::{ - asn1::Any, ByteSlice, Decodable, Decoder, Encodable, Encoder, Error, ErrorKind, Length, Result, - Tag, Tagged, -}; -use core::convert::TryFrom; - -/// ASN.1 `SEQUENCE` type. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct Sequence<'a> { - /// Inner value - inner: ByteSlice<'a>, -} - -impl<'a> Sequence<'a> { - /// Create a new [`Sequence`] from a slice. - pub(crate) fn new(slice: &'a [u8]) -> Result { - ByteSlice::new(slice) - .map(|inner| Self { inner }) - .map_err(|_| ErrorKind::Length { tag: Self::TAG }.into()) - } - - /// Borrow the inner byte sequence. - pub fn as_bytes(&self) -> &'a [u8] { - self.inner.as_bytes() - } - - /// Decode values nested within a sequence, creating a new [`Decoder`] for - /// the data contained in the sequence's body and passing it to the provided - /// [`FnOnce`]. - pub fn decode_nested(&self, f: F) -> Result - where - F: FnOnce(&mut Decoder<'a>) -> Result, - { - let mut seq_decoder = Decoder::new(self.as_bytes()); - let result = f(&mut seq_decoder)?; - seq_decoder.finish(result) - } - - /// Iterate over the values in a heterogenously typed sequence. - pub fn iter>(&self) -> SequenceIter<'a, T> { - SequenceIter::new(Decoder::new(self.as_bytes())) - } -} - -impl AsRef<[u8]> for Sequence<'_> { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl<'a> TryFrom> for Sequence<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result { - any.tag().assert_eq(Tag::Sequence)?; - Self::new(any.as_bytes()) - } -} - -impl<'a> From> for Any<'a> { - fn from(seq: Sequence<'a>) -> Any<'a> { - Any::from_tag_and_value(Tag::Sequence, seq.inner) - } -} - -impl<'a> Encodable for Sequence<'a> { - fn encoded_len(&self) -> Result { - Any::from(*self).encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Any::from(*self).encode(encoder) - } -} - -impl<'a> Tagged for Sequence<'a> { - const TAG: Tag = Tag::Sequence; -} diff --git a/der/src/asn1/sequence/iter.rs b/der/src/asn1/sequence/iter.rs deleted file mode 100644 index f14a09c6..00000000 --- a/der/src/asn1/sequence/iter.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! Sequence iterator. - -use crate::{Decodable, Decoder, Result}; -use core::marker::PhantomData; - -/// ASN.1 `SEQUENCE` iterator for [`Sequence`][`super::Sequence`] containing -/// homogeneously typed values. -pub struct SequenceIter<'a, T> -where - T: Decodable<'a>, -{ - /// Sequence decoder - decoder: Decoder<'a>, - - /// Type being decoded - decodable: PhantomData, -} - -impl<'a, T> SequenceIter<'a, T> -where - T: Decodable<'a>, -{ - /// Create a new sequence iterator for the given type. - pub(super) fn new(decoder: Decoder<'a>) -> Self { - Self { - decoder, - decodable: PhantomData, - } - } -} - -impl<'a, T> Iterator for SequenceIter<'a, T> -where - T: Decodable<'a>, -{ - type Item = Result; - - fn next(&mut self) -> Option> { - if self.decoder.is_finished() { - None - } else { - Some(T::decode(&mut self.decoder)) - } - } -} diff --git a/der/src/asn1/set_of.rs b/der/src/asn1/set_of.rs deleted file mode 100644 index 019c66fc..00000000 --- a/der/src/asn1/set_of.rs +++ /dev/null @@ -1,280 +0,0 @@ -//! ASN.1 `SET OF` support. - -use crate::{ - asn1::Any, ByteSlice, Decodable, Decoder, Encodable, Encoder, Error, ErrorKind, Length, Result, - Tag, Tagged, -}; -use core::{convert::TryFrom, marker::PhantomData}; - -#[cfg(feature = "alloc")] -use { - crate::Header, - alloc::collections::{btree_set, BTreeSet}, -}; - -/// ASN.1 `SET OF` denotes a collection of zero or more occurrences of a -/// given type. -/// -/// When encoded as DER, `SET OF` is lexicographically ordered. To implement -/// that requirement, types `T` which are elements of [`SetOf`] MUST provide -/// an impl of `Ord` which ensures that the corresponding DER encodings of -/// a given type are ordered. -pub trait SetOf<'a, 'b, T>: Decodable<'a> + Encodable -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - /// Iterator over the elements of the set. - /// - /// The iterator type MUST maintain the invariant that messages are - /// lexicographically ordered. - /// - /// See toplevel documentation about `Ord` trait requirements for - /// more information. - type Iter: Iterator; - - /// Iterate over the elements of the set. - fn elements(&'b self) -> Self::Iter; -} - -/// ASN.1 `SET OF` backed by a byte slice containing serialized DER. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct SetOfRef<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - /// DER-encoded byte slice - inner: ByteSlice<'a>, - - /// Set element type - element_type: PhantomData, -} - -impl<'a, T> SetOfRef<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - /// Create a new [`SetOfRef`] from a slice. - pub fn new(slice: &'a [u8]) -> Result { - let inner = ByteSlice::new(slice).map_err(|_| ErrorKind::Length { tag: Self::TAG })?; - let mut decoder = Decoder::new(slice); - let mut last_value = None; - - // Validate that we can decode all elements in the slice, and that they - // are lexicographically ordered according to DER's rules - while !decoder.is_finished() { - let value: T = decoder.decode()?; - - if let Some(last) = last_value.as_ref() { - if last >= &value { - return Err(Self::TAG.non_canonical_error()); - } - } - - last_value = Some(value); - } - - Ok(Self { - inner, - element_type: PhantomData, - }) - } - - /// Borrow the inner byte sequence. - pub fn as_bytes(&self) -> &'a [u8] { - self.inner.as_bytes() - } -} - -impl<'a, T> AsRef<[u8]> for SetOfRef<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl<'a, T> TryFrom> for SetOfRef<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - type Error = Error; - - fn try_from(any: Any<'a>) -> Result { - any.tag().assert_eq(Tag::Set)?; - Self::new(any.as_bytes()) - } -} - -impl<'a, T> From> for Any<'a> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - fn from(set: SetOfRef<'a, T>) -> Any<'a> { - Any::from_tag_and_value(Tag::Set, set.inner) - } -} - -impl<'a, T> Encodable for SetOfRef<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - fn encoded_len(&self) -> Result { - Any::from(self.clone()).encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Any::from(self.clone()).encode(encoder) - } -} - -impl<'a, 'b, T> SetOf<'a, 'b, T> for SetOfRef<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - type Iter = SetOfRefIter<'a, T>; - - fn elements(&'b self) -> Self::Iter { - SetOfRefIter::new(self) - } -} - -impl<'a, T> Tagged for SetOfRef<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - const TAG: Tag = Tag::Set; -} - -/// Iterator over the elements of an [`SetOfRef`]. -pub struct SetOfRefIter<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - /// Decoder which iterates over the elements of the message - decoder: Decoder<'a>, - - /// Element type - element_type: PhantomData, -} - -impl<'a, T> SetOfRefIter<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - pub(crate) fn new(set: &SetOfRef<'a, T>) -> Self { - Self { - decoder: Decoder::new(set.as_bytes()), - element_type: PhantomData, - } - } -} - -impl<'a, T> Iterator for SetOfRefIter<'a, T> -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - type Item = T; - - fn next(&mut self) -> Option { - if self.decoder.is_finished() { - None - } else { - Some( - self.decoder - .decode() - .expect("SetOfRef decodable invariant violated"), - ) - } - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl<'a, T> TryFrom> for BTreeSet -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - type Error = Error; - - fn try_from(any: Any<'a>) -> Result { - any.tag().assert_eq(Tag::Set)?; - - let mut result = BTreeSet::new(); - let mut decoder = Decoder::new(any.as_bytes()); - let mut last_value = None; - - while !decoder.is_finished() { - let value = decoder.decode()?; - - if let Some(last) = last_value.take() { - if last >= value { - return Err(Self::TAG.non_canonical_error()); - } - - result.insert(last); - } - - last_value = Some(value); - } - - if let Some(last) = last_value { - result.insert(last); - } - - Ok(result) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl<'a, T> Encodable for BTreeSet -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - fn encoded_len(&self) -> Result { - btreeset_inner_len(self)?.for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Header::new(Self::TAG, btreeset_inner_len(self)?)?.encode(encoder)?; - - for value in self.iter() { - encoder.encode(value)?; - } - - Ok(()) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl<'a, 'b, T: 'b> SetOf<'a, 'b, T> for BTreeSet -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - type Iter = core::iter::Cloned>; - - fn elements(&'b self) -> Self::Iter { - self.iter().cloned() - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl<'a, T> Tagged for BTreeSet -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - const TAG: Tag = Tag::Set; -} - -/// Get the encoded length of a [`BTreeSet`] -#[cfg(feature = "alloc")] -fn btreeset_inner_len<'a, T>(set: &BTreeSet) -> Result -where - T: Clone + Decodable<'a> + Encodable + Ord, -{ - set.iter() - .fold(Ok(Length::ZERO), |acc, val| acc? + val.encoded_len()?) -} diff --git a/der/src/asn1/utc_time.rs b/der/src/asn1/utc_time.rs deleted file mode 100644 index 0d26c667..00000000 --- a/der/src/asn1/utc_time.rs +++ /dev/null @@ -1,190 +0,0 @@ -//! ASN.1 `UTCTime` support. - -use crate::{ - asn1::Any, - datetime::{self, DateTime}, - Encodable, Encoder, Error, Header, Length, Result, Tag, Tagged, -}; -use core::{convert::TryFrom, time::Duration}; - -#[cfg(feature = "std")] -use std::time::{SystemTime, UNIX_EPOCH}; - -/// Maximum duration since `UNIX_EPOCH` which can be represented as a `UTCTime` -/// (non-inclusive) according to RFC 5280 rules. -/// -/// This corresponds to the RFC3339 date: `2050-01-01T00:00:00Z` -const MAX_UNIX_DURATION: Duration = Duration::from_secs(2_524_608_000); - -/// ASN.1 `UTCTime` type. -/// -/// This type implements the validity requirements specified in -/// [RFC 5280 Section 4.1.2.5.1][1], namely: -/// -/// > For the purposes of this profile, UTCTime values MUST be expressed in -/// > Greenwich Mean Time (Zulu) and MUST include seconds (i.e., times are -/// > `YYMMDDHHMMSSZ`), even where the number of seconds is zero. Conforming -/// > systems MUST interpret the year field (`YY`) as follows: -/// > -/// > - Where `YY` is greater than or equal to 50, the year SHALL be -/// > interpreted as `19YY`; and -/// > - Where `YY` is less than 50, the year SHALL be interpreted as `20YY`. -/// -/// [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1 -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct UtcTime(Duration); - -impl UtcTime { - /// Length of an RFC 5280-flavored ASN.1 DER-encoded [`UtcTime`]. - pub const LENGTH: Length = Length::new(13); - - /// Create a new [`UtcTime`] given a [`Duration`] since `UNIX_EPOCH` - /// (a.k.a. "Unix time") - pub fn new(unix_duration: Duration) -> Result { - if unix_duration < MAX_UNIX_DURATION { - Ok(Self(unix_duration)) - } else { - Err(Self::TAG.value_error()) - } - } - - /// Get the duration of this timestamp since `UNIX_EPOCH`. - pub fn unix_duration(&self) -> Duration { - self.0 - } - - /// Instantiate from [`SystemTime`]. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn from_system_time(time: SystemTime) -> Result { - time.duration_since(UNIX_EPOCH) - .map_err(|_| Self::TAG.value_error()) - .and_then(Self::new) - } - - /// Convert to [`SystemTime`]. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn to_system_time(&self) -> SystemTime { - UNIX_EPOCH + self.unix_duration() - } -} - -impl From<&UtcTime> for UtcTime { - fn from(value: &UtcTime) -> UtcTime { - *value - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl From for SystemTime { - fn from(utc_time: UtcTime) -> SystemTime { - utc_time.to_system_time() - } -} - -impl TryFrom> for UtcTime { - type Error = Error; - - fn try_from(any: Any<'_>) -> Result { - any.tag().assert_eq(Self::TAG)?; - - match *any.as_bytes() { - // RFC 5280 requires mandatory seconds and Z-normalized time zone - [year1, year2, mon1, mon2, day1, day2, hour1, hour2, min1, min2, sec1, sec2, b'Z'] => { - let year = datetime::decode_decimal(Self::TAG, year1, year2)?; - let month = datetime::decode_decimal(Self::TAG, mon1, mon2)?; - let day = datetime::decode_decimal(Self::TAG, day1, day2)?; - let hour = datetime::decode_decimal(Self::TAG, hour1, hour2)?; - let minute = datetime::decode_decimal(Self::TAG, min1, min2)?; - let second = datetime::decode_decimal(Self::TAG, sec1, sec2)?; - - // RFC 5280 rules for interpreting the year - let year = if year >= 50 { year + 1900 } else { year + 2000 }; - - DateTime::new(year, month, day, hour, minute, second) - .and_then(|dt| dt.unix_duration()) - .ok_or_else(|| Self::TAG.value_error()) - .and_then(Self::new) - } - _ => Err(Self::TAG.value_error()), - } - } -} - -impl Encodable for UtcTime { - fn encoded_len(&self) -> Result { - Self::LENGTH.for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Header::new(Self::TAG, Self::LENGTH)?.encode(encoder)?; - - let datetime = - DateTime::from_unix_duration(self.0).ok_or_else(|| Self::TAG.value_error())?; - - debug_assert!((1950..2050).contains(&datetime.year())); - - let year = match datetime.year() { - y @ 1950..=1999 => y - 1900, - y @ 2000..=2049 => y - 2000, - _ => return Err(Self::TAG.value_error()), - }; - - datetime::encode_decimal(encoder, Self::TAG, year)?; - datetime::encode_decimal(encoder, Self::TAG, datetime.month())?; - datetime::encode_decimal(encoder, Self::TAG, datetime.day())?; - datetime::encode_decimal(encoder, Self::TAG, datetime.hour())?; - datetime::encode_decimal(encoder, Self::TAG, datetime.minute())?; - datetime::encode_decimal(encoder, Self::TAG, datetime.second())?; - encoder.byte(b'Z') - } -} - -impl Tagged for UtcTime { - const TAG: Tag = Tag::UtcTime; -} - -#[cfg(test)] -mod tests { - use super::{DateTime, UtcTime}; - use crate::{Decodable, Encodable, Encoder}; - use hex_literal::hex; - - #[test] - fn round_trip_vector() { - let example_bytes = hex!("17 0d 39 31 30 35 30 36 32 33 34 35 34 30 5a"); - let utc_time = UtcTime::from_der(&example_bytes).unwrap(); - assert_eq!(utc_time.unix_duration().as_secs(), 673573540); - - let mut buf = [0u8; 128]; - let mut encoder = Encoder::new(&mut buf); - utc_time.encode(&mut encoder).unwrap(); - assert_eq!(example_bytes, encoder.finish().unwrap()); - } - - #[test] - fn round_trip_examples() { - for year in 1970..=2049 { - for month in 1..=12 { - let max_day = if month == 2 { 28 } else { 30 }; - - for day in 1..=max_day { - for hour in 0..=23 { - let datetime1 = DateTime::new(year, month, day, hour, 0, 0).unwrap(); - let utc_time1 = UtcTime::new(datetime1.unix_duration().unwrap()).unwrap(); - - let mut buf = [0u8; 128]; - let mut encoder = Encoder::new(&mut buf); - utc_time1.encode(&mut encoder).unwrap(); - let der_bytes = encoder.finish().unwrap(); - - let utc_time2 = UtcTime::from_der(der_bytes).unwrap(); - assert_eq!(utc_time1, utc_time2); - } - } - } - } - } -} diff --git a/der/src/asn1/utf8_string.rs b/der/src/asn1/utf8_string.rs deleted file mode 100644 index 1aa7f84b..00000000 --- a/der/src/asn1/utf8_string.rs +++ /dev/null @@ -1,195 +0,0 @@ -//! ASN.1 `UTF8String` support. - -use crate::{ - asn1::Any, str_slice::StrSlice, Encodable, Encoder, Error, Length, Result, Tag, Tagged, -}; -use core::{convert::TryFrom, fmt, str}; - -#[cfg(feature = "alloc")] -use alloc::{borrow::ToOwned, string::String}; - -/// ASN.1 `UTF8String` type. -/// -/// Supports the full UTF-8 encoding. -/// -/// Note that the [`Decodable`][`crate::Decodable`] and [`Encodable`] traits -/// are impl'd for Rust's [`str`][`prim@str`] primitive, which decodes/encodes -/// as a [`Utf8String`]. -/// -/// You are free to use [`str`][`prim@str`] instead of this type, however it's -/// still provided for explicitness in cases where it might be ambiguous with -/// other ASN.1 string encodings such as -/// [`PrintableString`][`crate::asn1::PrintableString`]. -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] -pub struct Utf8String<'a> { - /// Inner value - inner: StrSlice<'a>, -} - -impl<'a> Utf8String<'a> { - /// Create a new ASN.1 `UTF8String`. - pub fn new(input: &'a T) -> Result - where - T: AsRef<[u8]> + ?Sized, - { - StrSlice::from_bytes(input.as_ref()).map(|inner| Self { inner }) - } - - /// Borrow the string as a `str`. - pub fn as_str(&self) -> &'a str { - self.inner.as_str() - } - - /// Borrow the string as bytes. - pub fn as_bytes(&self) -> &'a [u8] { - self.inner.as_bytes() - } - - /// Get the length of the inner byte slice. - pub fn len(&self) -> Length { - self.inner.len() - } - - /// Is the inner string empty? - pub fn is_empty(&self) -> bool { - self.inner.is_empty() - } -} - -impl AsRef for Utf8String<'_> { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl AsRef<[u8]> for Utf8String<'_> { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl<'a> From<&Utf8String<'a>> for Utf8String<'a> { - fn from(value: &Utf8String<'a>) -> Utf8String<'a> { - *value - } -} - -impl<'a> TryFrom> for Utf8String<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - any.tag().assert_eq(Tag::Utf8String)?; - Self::new(any.as_bytes()) - } -} - -impl<'a> From> for Any<'a> { - fn from(printable_string: Utf8String<'a>) -> Any<'a> { - Any::from_tag_and_value(Tag::Utf8String, printable_string.inner.into()) - } -} - -impl<'a> From> for &'a [u8] { - fn from(utf8_string: Utf8String<'a>) -> &'a [u8] { - utf8_string.as_bytes() - } -} - -impl<'a> Encodable for Utf8String<'a> { - fn encoded_len(&self) -> Result { - Any::from(*self).encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Any::from(*self).encode(encoder) - } -} - -impl<'a> Tagged for Utf8String<'a> { - const TAG: Tag = Tag::Utf8String; -} - -impl<'a> fmt::Display for Utf8String<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -impl<'a> fmt::Debug for Utf8String<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Utf8String({:?})", self.as_str()) - } -} - -impl<'a> TryFrom> for &'a str { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result<&'a str> { - Utf8String::try_from(any).map(|s| s.as_str()) - } -} - -impl Encodable for str { - fn encoded_len(&self) -> Result { - Utf8String::new(self)?.encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Utf8String::new(self)?.encode(encoder) - } -} - -impl Tagged for str { - const TAG: Tag = Tag::Utf8String; -} - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl<'a> TryFrom> for String { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result { - Utf8String::try_from(any).map(|s| s.as_str().to_owned()) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl Encodable for String { - fn encoded_len(&self) -> Result { - Utf8String::new(self)?.encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - Utf8String::new(self)?.encode(encoder) - } -} - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -impl Tagged for String { - const TAG: Tag = Tag::Utf8String; -} - -#[cfg(test)] -mod tests { - use super::Utf8String; - use crate::Decodable; - - #[test] - fn parse_ascii_bytes() { - let example_bytes = &[ - 0x0c, 0x0b, 0x54, 0x65, 0x73, 0x74, 0x20, 0x55, 0x73, 0x65, 0x72, 0x20, 0x31, - ]; - - let utf8_string = Utf8String::from_der(example_bytes).unwrap(); - assert_eq!(utf8_string.as_str(), "Test User 1"); - } - - #[test] - fn parse_utf8_bytes() { - let example_bytes = &[0x0c, 0x06, 0x48, 0x65, 0x6c, 0x6c, 0xc3, 0xb3]; - let utf8_string = Utf8String::from_der(example_bytes).unwrap(); - assert_eq!(utf8_string.as_str(), "Helló"); - } -} diff --git a/der/src/byte_slice.rs b/der/src/byte_slice.rs deleted file mode 100644 index d300d3a5..00000000 --- a/der/src/byte_slice.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! Common handling for types backed by byte slices with enforcement of a -//! library-level length limitation i.e. `Length::max()`. - -use crate::{str_slice::StrSlice, Error, Length, Result}; -use core::convert::TryFrom; - -/// Byte slice newtype which respects the `Length::max()` limit. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub(crate) struct ByteSlice<'a> { - /// Inner value - inner: &'a [u8], - - /// Precomputed `Length` (avoids possible panicking conversions) - length: Length, -} - -impl<'a> ByteSlice<'a> { - /// Create a new [`ByteSlice`], ensuring that the provided `slice` value - /// is shorter than `Length::max()`. - pub fn new(slice: &'a [u8]) -> Result { - Ok(Self { - inner: slice, - length: Length::try_from(slice.len())?, - }) - } - - /// Borrow the inner byte slice - pub fn as_bytes(&self) -> &'a [u8] { - self.inner - } - - /// Get the [`Length`] of this [`ByteSlice`] - pub fn len(self) -> Length { - self.length - } - - /// Is this [`ByteSlice`] empty? - pub fn is_empty(self) -> bool { - self.len() == Length::ZERO - } -} - -impl AsRef<[u8]> for ByteSlice<'_> { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl Default for ByteSlice<'_> { - fn default() -> Self { - Self { - inner: &[], - length: Length::ZERO, - } - } -} - -impl<'a> TryFrom<&'a [u8]> for ByteSlice<'a> { - type Error = Error; - - fn try_from(slice: &'a [u8]) -> Result { - Self::new(slice) - } -} - -impl<'a> From> for ByteSlice<'a> { - fn from(s: StrSlice<'a>) -> ByteSlice<'a> { - let bytes = s.as_bytes(); - debug_assert_eq!(bytes.len(), usize::try_from(s.length).expect("overflow")); - - ByteSlice { - inner: bytes, - length: s.length, - } - } -} diff --git a/der/src/choice.rs b/der/src/choice.rs deleted file mode 100644 index 0306c60e..00000000 --- a/der/src/choice.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! ASN.1 `CHOICE` support. - -use crate::{Decodable, Encodable, Tag, Tagged}; - -/// ASN.1 `CHOICE` denotes a union of one or more possible alternatives. -/// -/// The types MUST have distinct tags. -/// -/// This crate models choice as a trait, with a blanket impl for all types -/// which impl `Decodable + Encodable + Tagged` (i.e. they are modeled as -/// a `CHOICE` with only one possible variant) -pub trait Choice<'a>: Decodable<'a> + Encodable { - /// Is the provided [`Tag`] decodable as a variant of this `CHOICE`? - fn can_decode(tag: Tag) -> bool; -} - -/// This blanket impl allows any [`Tagged`] type to function as a [`Choice`] -/// with a single alternative. -impl<'a, T> Choice<'a> for T -where - T: Decodable<'a> + Encodable + Tagged, -{ - fn can_decode(tag: Tag) -> bool { - T::TAG == tag - } -} diff --git a/der/src/datetime.rs b/der/src/datetime.rs deleted file mode 100644 index a265c70a..00000000 --- a/der/src/datetime.rs +++ /dev/null @@ -1,292 +0,0 @@ -//! Date and time functionality shared between various ASN.1 types -//! (e.g. `GeneralizedTime`, `UTCTime`) - -// Adapted from the `humantime` crate. -// Copyright (c) 2016 The humantime Developers -// Released under the MIT OR Apache 2.0 licenses - -use crate::{Encoder, Result, Tag}; -use core::time::Duration; - -/// Minimum year allowed in [`DateTime`] values. -const MIN_YEAR: u16 = 1970; - -/// Maximum duration since `UNIX_EPOCH` which can be represented as a -/// [`DateTime`] (non-inclusive). -const MAX_UNIX_DURATION: Duration = Duration::from_secs(253_402_300_800); - -/// Decode 2-digit decimal value -pub(crate) fn decode_decimal(tag: Tag, hi: u8, lo: u8) -> Result { - if (b'0'..=b'9').contains(&hi) && (b'0'..=b'9').contains(&lo) { - Ok((hi - b'0') as u16 * 10 + (lo - b'0') as u16) - } else { - Err(tag.value_error()) - } -} - -/// Encode 2-digit decimal value -pub(crate) fn encode_decimal(encoder: &mut Encoder<'_>, tag: Tag, value: u16) -> Result<()> { - let hi_val = value / 10; - - if hi_val >= 10 { - return Err(tag.value_error()); - } - - encoder.byte(hi_val as u8 + b'0')?; - encoder.byte((value % 10) as u8 + b'0') -} - -/// Inner date/time type shared by multiple ASN.1 types -/// (e.g. `GeneralizedTime`, `UTCTime`). -/// -/// Following conventions from RFC 5280, this type is always Z-normalized -/// (i.e. represents a UTC time). However, it isn't named "UTC time" in order -/// to prevent confusion with ASN.1 `UTCTime`. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub(crate) struct DateTime { - /// Full year (e.g. 2000). - /// - /// Must be >=1970 to permit positive conversions to Unix time. - year: u16, - - /// Month (1-12) - month: u16, - - /// Day of the month (1-31) - day: u16, - - /// Hour (0-23) - hour: u16, - - /// Minute (0-59) - minute: u16, - - /// Second (0-59) - second: u16, -} - -impl DateTime { - /// Create a new [`DateTime`] from the given UTC time components. - /// - /// Note that this does not fully validate the components of the date. - /// To ensure the date is valid, it must be converted to a Unix timestamp - /// by calling [`DateTime::unix_timestamp`]. - pub(crate) fn new( - year: u16, - month: u16, - day: u16, - hour: u16, - minute: u16, - second: u16, - ) -> Option { - // Basic validation of the components. - if year >= MIN_YEAR - && (1..=12).contains(&month) - && (1..=31).contains(&day) - && (0..=23).contains(&hour) - && (0..=59).contains(&minute) - && (0..=59).contains(&second) - { - Some(Self { - year, - month, - day, - hour, - minute, - second, - }) - } else { - None - } - } - - /// Compute a [`DateTime`] from the given [`Duration`] since the `UNIX_EPOCH`. - /// - /// Returns `None` if the value is outside the supported date range. - pub fn from_unix_duration(unix_duration: Duration) -> Option { - if unix_duration > MAX_UNIX_DURATION { - return None; - } - - let secs_since_epoch = unix_duration.as_secs(); - - /// 2000-03-01 (mod 400 year, immediately after Feb 29) - const LEAPOCH: i64 = 11017; - const DAYS_PER_400Y: i64 = 365 * 400 + 97; - const DAYS_PER_100Y: i64 = 365 * 100 + 24; - const DAYS_PER_4Y: i64 = 365 * 4 + 1; - - let days = (secs_since_epoch / 86400) as i64 - LEAPOCH; - let secs_of_day = secs_since_epoch % 86400; - - let mut qc_cycles = days / DAYS_PER_400Y; - let mut remdays = days % DAYS_PER_400Y; - - if remdays < 0 { - remdays += DAYS_PER_400Y; - qc_cycles -= 1; - } - - let mut c_cycles = remdays / DAYS_PER_100Y; - if c_cycles == 4 { - c_cycles -= 1; - } - remdays -= c_cycles * DAYS_PER_100Y; - - let mut q_cycles = remdays / DAYS_PER_4Y; - if q_cycles == 25 { - q_cycles -= 1; - } - remdays -= q_cycles * DAYS_PER_4Y; - - let mut remyears = remdays / 365; - if remyears == 4 { - remyears -= 1; - } - remdays -= remyears * 365; - - let mut year = 2000 + remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles; - - let months = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; - let mut mon = 0; - for mon_len in months.iter() { - mon += 1; - if remdays < *mon_len { - break; - } - remdays -= *mon_len; - } - let mday = remdays + 1; - let mon = if mon + 2 > 12 { - year += 1; - mon - 10 - } else { - mon + 2 - }; - - let second = secs_of_day % 60; - let mins_of_day = secs_of_day / 60; - let minute = mins_of_day % 60; - let hour = mins_of_day / 60; - - Self::new( - year as u16, - mon, - mday as u16, - hour as u16, - minute as u16, - second as u16, - ) - } - - /// Get the year - pub fn year(&self) -> u16 { - self.year - } - - /// Get the month - pub fn month(&self) -> u16 { - self.month - } - - /// Get the day - pub fn day(&self) -> u16 { - self.day - } - - /// Get the hour - pub fn hour(&self) -> u16 { - self.hour - } - - /// Get the minute - pub fn minute(&self) -> u16 { - self.minute - } - - /// Get the second - pub fn second(&self) -> u16 { - self.second - } - - /// Compute [`Duration`] since `UNIX_EPOCH` from the given calendar date. - pub(crate) fn unix_duration(&self) -> Option { - let leap_years = ((self.year - 1) - 1968) / 4 - ((self.year - 1) - 1900) / 100 - + ((self.year - 1) - 1600) / 400; - - let is_leap_year = self.is_leap_year(); - - let (mut ydays, mdays) = match self.month { - 1 => (0, 31), - 2 if is_leap_year => (31, 29), - 2 => (31, 28), - 3 => (59, 31), - 4 => (90, 30), - 5 => (120, 31), - 6 => (151, 30), - 7 => (181, 31), - 8 => (212, 31), - 9 => (243, 30), - 10 => (273, 31), - 11 => (304, 30), - 12 => (334, 31), - _ => return None, - }; - - if self.day > mdays || self.day == 0 { - return None; - } - - ydays += self.day - 1; - - if is_leap_year && self.month > 2 { - ydays += 1; - } - - let days = (self.year - 1970) as u64 * 365 + leap_years as u64 + ydays as u64; - let time = self.second as u64 + (self.minute as u64 * 60) + (self.hour as u64 * 3600); - Some(Duration::from_secs(time + days * 86400)) - } - - /// Is the year a leap year? - fn is_leap_year(&self) -> bool { - self.year % 4 == 0 && (self.year % 100 != 0 || self.year % 400 == 0) - } -} - -#[cfg(test)] -mod tests { - use super::DateTime; - - /// Ensure a day is OK - fn is_date_valid(year: u16, month: u16, day: u16, hour: u16, minute: u16, second: u16) -> bool { - DateTime::new(year, month, day, hour, minute, second) - .and_then(|dt| dt.unix_duration()) - .is_some() - } - - #[test] - fn feb_leap_year_handling() { - assert!(is_date_valid(2000, 2, 29, 0, 0, 0)); - assert!(!is_date_valid(2001, 2, 29, 0, 0, 0)); - assert!(!is_date_valid(2100, 2, 29, 0, 0, 0)); - } - - #[test] - fn round_trip() { - for year in 1970..=2100 { - for month in 1..=12 { - let max_day = if month == 2 { 28 } else { 30 }; - - for day in 1..=max_day { - for hour in 0..=23 { - let datetime1 = DateTime::new(year, month, day, hour, 0, 0).unwrap(); - let unix_duration = datetime1.unix_duration().unwrap(); - let datetime2 = DateTime::from_unix_duration(unix_duration).unwrap(); - assert_eq!(datetime1, datetime2); - } - } - } - } - } -} diff --git a/der/src/decodable.rs b/der/src/decodable.rs deleted file mode 100644 index ad09c5fc..00000000 --- a/der/src/decodable.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! Trait definition for [`Decodable`]. - -use crate::{asn1::Any, Decoder, Error, Result}; -use core::convert::TryFrom; - -/// Decoding trait. -/// -/// This trait provides the core abstraction upon which all decoding operations -/// are based. -/// -/// # Blanket impl for `TryFrom` -/// -/// In almost all cases you do not need to impl this trait yourself, but rather -/// can instead impl `TryFrom, Error = Error>` and receive a blanket -/// impl of this trait. -pub trait Decodable<'a>: Sized { - /// Attempt to decode this message using the provided decoder. - fn decode(decoder: &mut Decoder<'a>) -> Result; - - /// Parse `Self` from the provided DER-encoded byte slice. - fn from_der(bytes: &'a [u8]) -> Result { - let mut decoder = Decoder::new(bytes); - let result = Self::decode(&mut decoder)?; - decoder.finish(result) - } -} - -impl<'a, T> Decodable<'a> for T -where - T: TryFrom, Error = Error>, -{ - fn decode(decoder: &mut Decoder<'a>) -> Result { - Any::decode(decoder) - .and_then(Self::try_from) - .map_err(|e| decoder.error(e.kind())) - } -} diff --git a/der/src/decoder.rs b/der/src/decoder.rs deleted file mode 100644 index a5b4b11a..00000000 --- a/der/src/decoder.rs +++ /dev/null @@ -1,352 +0,0 @@ -//! DER decoder. - -use crate::{asn1::*, Choice, Decodable, Error, ErrorKind, Length, Result, Tag, TagNumber}; -use core::{ - cmp::Ordering, - convert::{TryFrom, TryInto}, -}; - -/// DER decoder. -#[derive(Debug)] -pub struct Decoder<'a> { - /// Byte slice being decoded. - /// - /// In the event an error was previously encountered this will be set to - /// `None` to prevent further decoding while in a bad state. - bytes: Option<&'a [u8]>, - - /// Position within the decoded slice. - position: Length, -} - -impl<'a> Decoder<'a> { - /// Create a new decoder for the given byte slice. - pub fn new(bytes: &'a [u8]) -> Self { - Self { - bytes: Some(bytes), - position: Length::ZERO, - } - } - - /// Decode a value which impls the [`Decodable`] trait. - pub fn decode>(&mut self) -> Result { - if self.is_failed() { - return Err(self.error(ErrorKind::Failed)); - } - - T::decode(self).map_err(|e| { - self.bytes.take(); - e.nested(self.position) - }) - } - - /// Return an error with the given [`ErrorKind`], annotating it with - /// context about where the error occurred. - pub fn error(&mut self, kind: ErrorKind) -> Error { - self.bytes.take(); - kind.at(self.position) - } - - /// Return an error for an invalid value with the given tag. - pub fn value_error(&mut self, tag: Tag) -> Error { - self.error(tag.value_error().kind()) - } - - /// Did the decoding operation fail due to an error? - pub fn is_failed(&self) -> bool { - self.bytes.is_none() - } - - /// Finish decoding, returning the given value if there is no - /// remaining data, or an error otherwise - pub fn finish(self, value: T) -> Result { - if self.is_failed() { - Err(ErrorKind::Failed.at(self.position)) - } else if !self.is_finished() { - Err(ErrorKind::TrailingData { - decoded: self.position, - remaining: self.remaining_len()?, - } - .at(self.position)) - } else { - Ok(value) - } - } - - /// Have we decoded all of the bytes in this [`Decoder`]? - /// - /// Returns `false` if we're not finished decoding or if a fatal error - /// has occurred. - pub fn is_finished(&self) -> bool { - self.remaining().map(|rem| rem.is_empty()).unwrap_or(false) - } - - /// Attempt to decode an ASN.1 `ANY` value. - pub fn any(&mut self) -> Result> { - self.decode() - } - - /// Attempt to decode an `OPTIONAL` ASN.1 `ANY` value. - pub fn any_optional(&mut self) -> Result>> { - self.decode() - } - - /// Attempt to decode ASN.1 `INTEGER` as `i8` - pub fn int8(&mut self) -> Result { - self.decode() - } - - /// Attempt to decode ASN.1 `INTEGER` as `i16` - pub fn int16(&mut self) -> Result { - self.decode() - } - - /// Attempt to decode unsigned ASN.1 `INTEGER` as `u8` - pub fn uint8(&mut self) -> Result { - self.decode() - } - - /// Attempt to decode unsigned ASN.1 `INTEGER` as `u16` - pub fn uint16(&mut self) -> Result { - self.decode() - } - - /// Attempt to decode an ASN.1 `INTEGER` as a [`UIntBytes`]. - #[cfg(feature = "bigint")] - #[cfg_attr(docsrs, doc(cfg(feature = "bigint")))] - pub fn uint_bytes(&mut self) -> Result> { - self.decode() - } - - /// Attempt to decode an ASN.1 `BIT STRING`. - pub fn bit_string(&mut self) -> Result> { - self.decode() - } - - /// Attempt to decode an ASN.1 `CONTEXT-SPECIFIC` field with the - /// provided [`TagNumber`]. - /// - /// This method has the following behavior which is designed to simplify - /// handling of extension fields, which are denoted in an ASN.1 schema - /// using the `...` ellipsis extension marker: - /// - /// - Skips over [`ContextSpecific`] fields with a tag number lower than - /// the current one, consuming and ignoring them. - /// - Returns `Ok(None)` if a [`ContextSpecific`] field with a higher tag - /// number is encountered. These fields are not consumed in this case, - /// allowing a field with a lower tag number to be omitted, then the - /// higher numbered field consumed as a follow-up. - /// - Returns `Ok(None)` if anything other than a [`ContextSpecific`] field - /// is encountered. - pub fn context_specific(&mut self, tag: TagNumber) -> Result>> { - loop { - match self.peek().map(Tag::try_from).transpose()? { - Some(Tag::ContextSpecific(actual_tag)) => { - match actual_tag.cmp(&tag) { - Ordering::Less => { - // Decode and ignore lower-numbered fields if - // they parse correctly. - self.decode::>()?; - } - Ordering::Equal => { - return self - .decode::>() - .map(|cs| Some(cs.value)) - } - Ordering::Greater => return Ok(None), - } - } - _ => return Ok(None), - } - } - } - - /// Attempt to decode an ASN.1 `GeneralizedTime`. - pub fn generalized_time(&mut self) -> Result { - self.decode() - } - - /// Attempt to decode an ASN.1 `IA5String`. - pub fn ia5_string(&mut self) -> Result> { - self.decode() - } - - /// Attempt to decode an ASN.1 `NULL` value. - pub fn null(&mut self) -> Result { - self.decode() - } - - /// Attempt to decode an ASN.1 `OCTET STRING`. - pub fn octet_string(&mut self) -> Result> { - self.decode() - } - - /// Attempt to decode an ASN.1 `OBJECT IDENTIFIER`. - #[cfg(feature = "oid")] - #[cfg_attr(docsrs, doc(cfg(feature = "oid")))] - pub fn oid(&mut self) -> Result { - self.decode() - } - - /// Attempt to decode an ASN.1 `OPTIONAL` value. - pub fn optional>(&mut self) -> Result> { - self.decode() - } - - /// Attempt to decode an ASN.1 `PrintableString`. - pub fn printable_string(&mut self) -> Result> { - self.decode() - } - - /// Attempt to decode an ASN.1 `UTCTime`. - pub fn utc_time(&mut self) -> Result { - self.decode() - } - - /// Attempt to decode an ASN.1 `UTF8String`. - pub fn utf8_string(&mut self) -> Result> { - self.decode() - } - - /// Attempt to decode an ASN.1 `SEQUENCE`, creating a new nested - /// [`Decoder`] and calling the provided argument with it. - pub fn sequence(&mut self, f: F) -> Result - where - F: FnOnce(&mut Decoder<'a>) -> Result, - { - Sequence::decode(self)?.decode_nested(f).map_err(|e| { - self.bytes.take(); - e.nested(self.position) - }) - } - - /// Decode a single byte, updating the internal cursor. - pub(crate) fn byte(&mut self) -> Result { - match self.bytes(1u8)? { - [byte] => Ok(*byte), - _ => Err(self.error(ErrorKind::Truncated)), - } - } - - /// Obtain a slice of bytes of the given length from the current cursor - /// position, or return an error if we have insufficient data. - pub(crate) fn bytes(&mut self, len: impl TryInto) -> Result<&'a [u8]> { - if self.is_failed() { - return Err(self.error(ErrorKind::Failed)); - } - - let len = len - .try_into() - .map_err(|_| self.error(ErrorKind::Overflow))?; - - let result = self - .remaining()? - .get(..len.try_into()?) - .ok_or(ErrorKind::Truncated)?; - - self.position = (self.position + len)?; - Ok(result) - } - - /// Peek at the next byte in the decoder without modifying the cursor. - pub(crate) fn peek(&self) -> Option { - self.remaining() - .ok() - .and_then(|bytes| bytes.get(0).cloned()) - } - - /// Obtain the remaining bytes in this decoder from the current cursor - /// position. - fn remaining(&self) -> Result<&'a [u8]> { - let pos = usize::try_from(self.position)?; - - self.bytes - .and_then(|b| b.get(pos..)) - .ok_or_else(|| ErrorKind::Truncated.at(self.position)) - } - - /// Get the number of bytes still remaining in the buffer. - fn remaining_len(&self) -> Result { - self.remaining()?.len().try_into() - } -} - -impl<'a> From<&'a [u8]> for Decoder<'a> { - fn from(bytes: &'a [u8]) -> Decoder<'a> { - Decoder::new(bytes) - } -} - -#[cfg(test)] -mod tests { - use super::Decoder; - use crate::{Decodable, ErrorKind, Length, Tag, TagNumber}; - use core::convert::TryFrom; - use hex_literal::hex; - - #[test] - fn context_specific_with_expected_field() { - let tag = TagNumber::new(0); - - // Empty message - let mut decoder = Decoder::new(&[]); - assert_eq!(decoder.context_specific(tag).unwrap(), None); - - // Message containing a non-context-specific type - let mut decoder = Decoder::new(&hex!("020100")); - assert_eq!(decoder.context_specific(tag).unwrap(), None); - - // - let mut decoder = Decoder::new(&hex!("A003020100")); - let field = decoder.context_specific(tag).unwrap().unwrap(); - assert_eq!(u8::try_from(field).unwrap(), 0); - } - - #[test] - fn context_specific_skipping_unknown_field() { - let tag = TagNumber::new(1); - let mut decoder = Decoder::new(&hex!("A003020100A103020101")); - let field = decoder.context_specific(tag).unwrap().unwrap(); - assert_eq!(u8::try_from(field).unwrap(), 1); - } - - #[test] - fn context_specific_returns_none_on_greater_tag_number() { - let tag = TagNumber::new(0); - let mut decoder = Decoder::new(&hex!("A103020101")); - assert_eq!(decoder.context_specific(tag).unwrap(), None); - } - - #[test] - fn truncated_message() { - let mut decoder = Decoder::new(&[]); - let err = bool::decode(&mut decoder).err().unwrap(); - assert_eq!(ErrorKind::Truncated, err.kind()); - assert_eq!(Some(Length::ZERO), err.position()); - } - - #[test] - fn invalid_field_length() { - let mut decoder = Decoder::new(&[0x02, 0x01]); - let err = i8::decode(&mut decoder).err().unwrap(); - assert_eq!(ErrorKind::Length { tag: Tag::Integer }, err.kind()); - assert_eq!(Some(Length::from(2u8)), err.position()); - } - - #[test] - fn trailing_data() { - let mut decoder = Decoder::new(&[0x02, 0x01, 0x2A, 0x00]); - let x = decoder.decode().unwrap(); - assert_eq!(42i8, x); - - let err = decoder.finish(x).err().unwrap(); - assert_eq!( - ErrorKind::TrailingData { - decoded: 3u8.into(), - remaining: 1u8.into() - }, - err.kind() - ); - assert_eq!(Some(Length::from(3u8)), err.position()); - } -} diff --git a/der/src/encodable.rs b/der/src/encodable.rs deleted file mode 100644 index 75149fde..00000000 --- a/der/src/encodable.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Trait definition for [`Encodable`]. - -use crate::{Encoder, Length, Result}; - -#[cfg(feature = "alloc")] -use { - crate::ErrorKind, - alloc::vec::Vec, - core::{ - convert::{TryFrom, TryInto}, - iter, - }, -}; - -/// Encoding trait. -pub trait Encodable { - /// Compute the length of this value in bytes when encoded as ASN.1 DER. - fn encoded_len(&self) -> Result; - - /// Encode this value as ASN.1 DER using the provided [`Encoder`]. - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()>; - - /// Encode this value to the provided byte slice, returning a sub-slice - /// containing the encoded message. - fn encode_to_slice<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> { - let mut encoder = Encoder::new(buf); - self.encode(&mut encoder)?; - encoder.finish() - } - - /// Encode this message as ASN.1 DER, appending it to the provided - /// byte vector. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn encode_to_vec(&self, buf: &mut Vec) -> Result { - let expected_len = usize::try_from(self.encoded_len()?)?; - buf.reserve(expected_len); - buf.extend(iter::repeat(0).take(expected_len)); - - let mut encoder = Encoder::new(buf); - self.encode(&mut encoder)?; - let actual_len = encoder.finish()?.len(); - - if expected_len != actual_len { - return Err(ErrorKind::Underlength { - expected: expected_len.try_into()?, - actual: actual_len.try_into()?, - } - .into()); - } - - actual_len.try_into() - } - - /// Serialize this message as a byte vector. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn to_vec(&self) -> Result> { - let mut buf = Vec::new(); - self.encode_to_vec(&mut buf)?; - Ok(buf) - } -} diff --git a/der/src/encoder.rs b/der/src/encoder.rs deleted file mode 100644 index cbf16f12..00000000 --- a/der/src/encoder.rs +++ /dev/null @@ -1,253 +0,0 @@ -//! DER encoder. - -use crate::{asn1::*, message, Encodable, Error, ErrorKind, Header, Length, Result, Tag}; -use core::convert::{TryFrom, TryInto}; - -/// DER encoder. -#[derive(Debug)] -pub struct Encoder<'a> { - /// Buffer into which DER-encoded message is written - bytes: Option<&'a mut [u8]>, - - /// Total number of bytes written to buffer so far - position: Length, -} - -impl<'a> Encoder<'a> { - /// Create a new encoder with the given byte slice as a backing buffer. - pub fn new(bytes: &'a mut [u8]) -> Self { - Self { - bytes: Some(bytes), - position: Length::ZERO, - } - } - - /// Encode a value which impls the [`Encodable`] trait. - pub fn encode(&mut self, encodable: &T) -> Result<()> { - if self.is_failed() { - self.error(ErrorKind::Failed)?; - } - - encodable.encode(self).map_err(|e| { - self.bytes.take(); - e.nested(self.position) - }) - } - - /// Return an error with the given [`ErrorKind`], annotating it with - /// context about where the error occurred. - // TODO(tarcieri): change return type to `Error` - pub fn error(&mut self, kind: ErrorKind) -> Result { - self.bytes.take(); - Err(kind.at(self.position)) - } - - /// Return an error for an invalid value with the given tag. - // TODO(tarcieri): compose this with `Encoder::error` after changing its return type - pub fn value_error(&mut self, tag: Tag) -> Error { - self.bytes.take(); - tag.value_error().kind().at(self.position) - } - - /// Did the decoding operation fail due to an error? - pub fn is_failed(&self) -> bool { - self.bytes.is_none() - } - - /// Finish encoding to the buffer, returning a slice containing the data - /// written to the buffer. - pub fn finish(self) -> Result<&'a [u8]> { - let pos = self.position; - let range = ..usize::try_from(self.position)?; - - match self.bytes { - Some(bytes) => bytes.get(range).ok_or_else(|| ErrorKind::Truncated.at(pos)), - None => Err(ErrorKind::Failed.at(pos)), - } - } - - /// Encode the provided value as an ASN.1 `BIT STRING` - pub fn bit_string(&mut self, value: impl TryInto>) -> Result<()> { - value - .try_into() - .map_err(|_| self.value_error(Tag::BitString)) - .and_then(|value| self.encode(&value)) - } - - /// Encode the provided value as an ASN.1 `GeneralizedTime` - pub fn generalized_time(&mut self, value: impl TryInto) -> Result<()> { - value - .try_into() - .map_err(|_| self.value_error(Tag::GeneralizedTime)) - .and_then(|value| self.encode(&value)) - } - - /// Encode the provided value as an ASN.1 `IA5String` - pub fn ia5_string(&mut self, value: impl TryInto>) -> Result<()> { - value - .try_into() - .map_err(|_| self.value_error(Tag::Ia5String)) - .and_then(|value| self.encode(&value)) - } - - /// Encode a message with the provided [`Encodable`] fields as an - /// ASN.1 `SEQUENCE`. - pub fn message(&mut self, fields: &[&dyn Encodable]) -> Result<()> { - let length = message::encoded_len_inner(fields)?; - - self.sequence(length, |nested_encoder| { - for field in fields { - field.encode(nested_encoder)?; - } - - Ok(()) - }) - } - - /// Encode an ASN.1 `NULL` value. - pub fn null(&mut self) -> Result<()> { - self.encode(&Null) - } - - /// Encode the provided value as an ASN.1 `OCTET STRING` - pub fn octet_string(&mut self, value: impl TryInto>) -> Result<()> { - value - .try_into() - .map_err(|_| self.value_error(Tag::OctetString)) - .and_then(|value| self.encode(&value)) - } - - /// Encode an ASN.1 [`ObjectIdentifier`] - #[cfg(feature = "oid")] - #[cfg_attr(docsrs, doc(cfg(feature = "oid")))] - pub fn oid(&mut self, value: impl TryInto) -> Result<()> { - value - .try_into() - .map_err(|_| self.value_error(Tag::ObjectIdentifier)) - .and_then(|value| self.encode(&value)) - } - - /// Encode the provided value as an ASN.1 `PrintableString` - pub fn printable_string(&mut self, value: impl TryInto>) -> Result<()> { - value - .try_into() - .map_err(|_| self.value_error(Tag::PrintableString)) - .and_then(|value| self.encode(&value)) - } - - /// Encode an ASN.1 `SEQUENCE` of the given length. - /// - /// Spawns a nested [`Encoder`] which is expected to be exactly the - /// specified length upon completion. - pub fn sequence(&mut self, length: Length, f: F) -> Result<()> - where - F: FnOnce(&mut Encoder<'_>) -> Result<()>, - { - Header::new(Tag::Sequence, length).and_then(|header| header.encode(self))?; - - let mut nested_encoder = Encoder::new(self.reserve(length)?); - f(&mut nested_encoder)?; - - if nested_encoder.finish()?.len() == length.try_into()? { - Ok(()) - } else { - self.error(ErrorKind::Length { tag: Tag::Sequence }) - } - } - - /// Encode the provided value as an ASN.1 `UTCTime` - pub fn utc_time(&mut self, value: impl TryInto) -> Result<()> { - value - .try_into() - .map_err(|_| self.value_error(Tag::UtcTime)) - .and_then(|value| self.encode(&value)) - } - - /// Encode the provided value as an ASN.1 `Utf8String` - pub fn utf8_string(&mut self, value: impl TryInto>) -> Result<()> { - value - .try_into() - .map_err(|_| self.value_error(Tag::Utf8String)) - .and_then(|value| self.encode(&value)) - } - - /// Reserve a portion of the internal buffer, updating the internal cursor - /// position and returning a mutable slice. - fn reserve(&mut self, len: impl TryInto) -> Result<&mut [u8]> { - let len = len - .try_into() - .or_else(|_| self.error(ErrorKind::Overflow))?; - - if len > self.remaining_len()? { - self.error(ErrorKind::Overlength)?; - } - - let end = (self.position + len).or_else(|e| self.error(e.kind()))?; - let range = self.position.try_into()?..end.try_into()?; - let position = &mut self.position; - - // TODO(tarcieri): non-panicking version of this code - // We ensure above that the buffer is untainted and there is sufficient - // space to perform this slicing operation, however it would be nice to - // have fully panic-free code. - // - // Unfortunately tainting the buffer on error is tricky to do when - // potentially holding a reference to the buffer, and failure to taint - // it would not uphold the invariant that any errors should taint it. - let slice = &mut self.bytes.as_mut().expect("DER encoder tainted")[range]; - *position = end; - - Ok(slice) - } - - /// Encode a single byte into the backing buffer. - pub(crate) fn byte(&mut self, byte: u8) -> Result<()> { - match self.reserve(1u8)?.first_mut() { - Some(b) => { - *b = byte; - Ok(()) - } - None => self.error(ErrorKind::Truncated), - } - } - - /// Encode the provided byte slice into the backing buffer. - pub(crate) fn bytes(&mut self, slice: &[u8]) -> Result<()> { - self.reserve(slice.len())?.copy_from_slice(slice); - Ok(()) - } - - /// Get the size of the buffer in bytes. - fn buffer_len(&self) -> Result { - self.bytes - .as_ref() - .map(|bytes| bytes.len()) - .ok_or_else(|| ErrorKind::Failed.at(self.position)) - .and_then(TryInto::try_into) - } - - /// Get the number of bytes still remaining in the buffer. - fn remaining_len(&self) -> Result { - let buffer_len = usize::try_from(self.buffer_len()?)?; - - buffer_len - .checked_sub(self.position.try_into()?) - .ok_or_else(|| ErrorKind::Truncated.at(self.position)) - .and_then(TryInto::try_into) - } -} - -#[cfg(test)] -mod tests { - use super::Encoder; - use crate::{Encodable, ErrorKind, Length}; - - #[test] - fn overlength_message() { - let mut buffer = []; - let mut encoder = Encoder::new(&mut buffer); - let err = false.encode(&mut encoder).err().unwrap(); - assert_eq!(err.kind(), ErrorKind::Overlength); - assert_eq!(err.position(), Some(Length::ZERO)); - } -} diff --git a/der/src/error.rs b/der/src/error.rs deleted file mode 100644 index 7dc2c8f1..00000000 --- a/der/src/error.rs +++ /dev/null @@ -1,259 +0,0 @@ -//! Error types. - -pub use core::str::Utf8Error; - -use crate::{Length, Tag}; -use core::{convert::Infallible, fmt}; - -#[cfg(feature = "oid")] -use crate::asn1::ObjectIdentifier; - -/// Result type. -pub type Result = core::result::Result; - -/// Error type. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Error { - /// Kind of error. - kind: ErrorKind, - - /// Position inside of message where error occurred. - position: Option, -} - -impl Error { - /// Create a new [`Error`]. - pub fn new(kind: ErrorKind, position: Length) -> Error { - Error { - kind, - position: Some(position), - } - } - - /// Get the [`ErrorKind`] which occurred. - pub fn kind(self) -> ErrorKind { - self.kind - } - - /// Get the position inside of the message where the error occurred. - pub fn position(self) -> Option { - self.position - } - - /// For errors occurring inside of a nested message, extend the position - /// count by the location where the nested message occurs. - pub fn nested(self, nested_position: Length) -> Self { - // TODO(tarcieri): better handle length overflows occurring in this calculation? - let position = (nested_position + self.position.unwrap_or_default()).ok(); - - Self { - kind: self.kind, - position, - } - } -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.kind)?; - - if let Some(pos) = self.position { - write!(f, " at DER byte {}", pos)?; - } - - Ok(()) - } -} - -impl From for Error { - fn from(kind: ErrorKind) -> Error { - Error { - kind, - position: None, - } - } -} - -impl From for Error { - fn from(_: Infallible) -> Error { - unreachable!() - } -} - -impl From for Error { - fn from(err: Utf8Error) -> Error { - Error { - kind: ErrorKind::Utf8(err), - position: None, - } - } -} - -#[cfg(feature = "oid")] -impl From for Error { - fn from(_: const_oid::Error) -> Error { - ErrorKind::MalformedOid.into() - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ErrorKind {} - -/// Error type. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub enum ErrorKind { - /// Indicates a field which is duplicated when only one is expected. - DuplicateField { - /// Tag of the duplicated field. - tag: Tag, - }, - - /// This error indicates a previous DER parsing operation resulted in - /// an error and tainted the state of a `Decoder` or `Encoder`. - /// - /// Once this occurs, the overall operation has failed and cannot be - /// subsequently resumed. - Failed, - - /// Incorrect length for a given field. - Length { - /// Tag of the value being decoded. - tag: Tag, - }, - - /// Message is not canonically encoded. - Noncanonical { - /// Tag of the value which is not canonically encoded. - tag: Tag, - }, - - /// Malformed OID - MalformedOid, - - /// Integer overflow occurred (library bug!). - Overflow, - - /// Message is longer than this library's internal limits support. - Overlength, - - /// Undecoded trailing data at end of message. - TrailingData { - /// Length of the decoded data. - decoded: Length, - - /// Total length of the remaining data left in the buffer. - remaining: Length, - }, - - /// Unexpected end-of-message/nested field when decoding. - Truncated, - - /// Encoded message is shorter than the expected length. - /// - /// (i.e. an `Encodable` impl on a particular type has a buggy `encoded_len`) - Underlength { - /// Expected length - expected: Length, - - /// Actual length - actual: Length, - }, - - /// Unexpected tag. - UnexpectedTag { - /// Tag the decoder was expecting (if there is a single such tag). - /// - /// `None` if multiple tags are expected/allowed, but the `actual` tag - /// does not match any of them. - expected: Option, - - /// Actual tag encountered in the message. - actual: Tag, - }, - - /// Unknown OID. - /// - /// This error is intended to be used by libraries which parse DER-based - /// formats which encounter unknown or unsupported OID libraries. - /// - /// It enables passing back the OID value to the caller, which allows them - /// to determine which OID(s) are causing the error (and then potentially - /// contribute upstream support for algorithms they care about). - #[cfg(feature = "oid")] - #[cfg_attr(docsrs, doc(cfg(feature = "oid")))] - UnknownOid { - /// OID value that was unrecognized by a parser for a DER-based format. - oid: ObjectIdentifier, - }, - - /// Unknown/unsupported tag. - UnknownTag { - /// Raw byte value of the tag. - byte: u8, - }, - - /// UTF-8 errors. - Utf8(Utf8Error), - - /// Unexpected value. - Value { - /// Tag of the unexpected value. - tag: Tag, - }, -} - -impl ErrorKind { - /// Annotate an [`ErrorKind`] with context about where it occurred, - /// returning an error. - pub fn at(self, position: Length) -> Error { - Error::new(self, position) - } -} - -impl fmt::Display for ErrorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ErrorKind::DuplicateField { tag } => write!(f, "duplicate field for {}", tag), - ErrorKind::Failed => write!(f, "operation failed"), - ErrorKind::Length { tag } => write!(f, "incorrect length for {}", tag), - ErrorKind::Noncanonical { tag } => { - write!(f, "ASN.1 {} not canonically encoded as DER", tag) - } - ErrorKind::MalformedOid => write!(f, "malformed OID"), - ErrorKind::Overflow => write!(f, "integer overflow"), - ErrorKind::Overlength => write!(f, "DER message is too long"), - ErrorKind::TrailingData { decoded, remaining } => { - write!( - f, - "trailing data at end of DER message: decoded {} bytes, {} bytes remaining", - decoded, remaining - ) - } - ErrorKind::Truncated => write!(f, "DER message is truncated"), - ErrorKind::Underlength { expected, actual } => write!( - f, - "DER message too short: expected {}, got {}", - expected, actual - ), - ErrorKind::UnexpectedTag { expected, actual } => { - write!(f, "unexpected ASN.1 DER tag: ")?; - - if let Some(tag) = expected { - write!(f, "expected {}, ", tag)?; - } - - write!(f, "got {}", actual) - } - #[cfg(feature = "oid")] - ErrorKind::UnknownOid { oid } => { - write!(f, "unknown/unsupported OID: {}", oid) - } - ErrorKind::UnknownTag { byte } => { - write!(f, "unknown/unsupported ASN.1 DER tag: 0x{:02x}", byte) - } - ErrorKind::Utf8(e) => write!(f, "{}", e), - ErrorKind::Value { tag } => write!(f, "malformed ASN.1 DER value for {}", tag), - } - } -} diff --git a/der/src/header.rs b/der/src/header.rs deleted file mode 100644 index c08f7999..00000000 --- a/der/src/header.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! ASN.1 DER headers. - -use crate::{Decodable, Decoder, Encodable, Encoder, ErrorKind, Length, Result, Tag}; -use core::convert::TryInto; - -/// ASN.1 DER headers: tag + length component of TLV-encoded values -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Header { - /// Tag representing the type of the encoded value - pub tag: Tag, - - /// Length of the encoded value - pub length: Length, -} - -impl Header { - /// Create a new [`Header`] from a [`Tag`] and a specified length. - /// - /// Returns an error if the length exceeds the limits of [`Length`]. - pub fn new(tag: Tag, length: impl TryInto) -> Result { - let length = length.try_into().map_err(|_| ErrorKind::Overflow)?; - Ok(Self { tag, length }) - } -} - -impl Decodable<'_> for Header { - fn decode(decoder: &mut Decoder<'_>) -> Result
{ - let tag = Tag::decode(decoder)?; - - let length = Length::decode(decoder).map_err(|e| { - if e.kind() == ErrorKind::Overlength { - ErrorKind::Length { tag }.into() - } else { - e - } - })?; - - Ok(Self { tag, length }) - } -} - -impl Encodable for Header { - fn encoded_len(&self) -> Result { - self.tag.encoded_len()? + self.length.encoded_len()? - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - self.tag.encode(encoder)?; - self.length.encode(encoder) - } -} diff --git a/der/src/length.rs b/der/src/length.rs deleted file mode 100644 index 8b01cd84..00000000 --- a/der/src/length.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! Length calculations for encoded ASN.1 DER values - -use crate::{Decodable, Decoder, Encodable, Encoder, Error, ErrorKind, Result}; -use core::{ - convert::{TryFrom, TryInto}, - fmt, - ops::Add, -}; - -/// Maximum length as a `u32` (1 MiB). -const MAX_U32: u32 = 0xf_ffff; - -/// ASN.1-encoded length. -/// -/// Maximum length is defined by the [`Length::MAX`] constant (1 MiB). -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] -pub struct Length(u32); - -impl Length { - /// Length of `0` - pub const ZERO: Self = Self(0); - - /// Length of `1` - pub const ONE: Self = Self(1); - - /// Maximum length currently supported: 1 MiB - pub const MAX: Self = Self(MAX_U32); - - /// Create a new [`Length`] for any value which fits inside of a [`u16`]. - /// - /// This function is const-safe and therefore useful for [`Length`] constants. - pub const fn new(value: u16) -> Self { - Length(value as u32) - } - - /// Get the length of DER Tag-Length-Value (TLV) encoded data if `self` - /// is the length of the inner "value" portion of the message. - pub fn for_tlv(self) -> Result { - Length(1) + self.encoded_len()? + self - } - - /// Get initial octet of the encoded length (if one is required). - /// - /// From X.690 Section 8.1.3.5: - /// > In the long form, the length octets shall consist of an initial octet - /// > and one or more subsequent octets. The initial octet shall be encoded - /// > as follows: - /// > - /// > a) bit 8 shall be one; - /// > b) bits 7 to 1 shall encode the number of subsequent octets in the - /// > length octets, as an unsigned binary integer with bit 7 as the - /// > most significant bit; - /// > c) the value 11111111₂ shall not be used. - fn initial_octet(self) -> Option { - match self.0 { - 0x80..=0xFF => Some(0x81), - 0x100..=0xFFFF => Some(0x82), - 0x10000..=MAX_U32 => Some(0x83), - _ => None, - } - } -} - -impl Add for Length { - type Output = Result; - - fn add(self, other: Self) -> Result { - self.0 - .checked_add(other.0) - .ok_or_else(|| ErrorKind::Overflow.into()) - .and_then(TryInto::try_into) - } -} - -impl Add for Length { - type Output = Result; - - fn add(self, other: u8) -> Result { - self + Length::from(other) - } -} - -impl Add for Length { - type Output = Result; - - fn add(self, other: u16) -> Result { - self + Length::from(other) - } -} - -impl Add for Length { - type Output = Result; - - fn add(self, other: u32) -> Result { - self + Length::try_from(other)? - } -} - -impl Add for Length { - type Output = Result; - - fn add(self, other: usize) -> Result { - self + Length::try_from(other)? - } -} - -impl Add for Result { - type Output = Self; - - fn add(self, other: Length) -> Self { - self? + other - } -} - -impl From for Length { - fn from(len: u8) -> Length { - Length(len as u32) - } -} - -impl From for Length { - fn from(len: u16) -> Length { - Length(len as u32) - } -} - -impl TryFrom for Length { - type Error = Error; - - fn try_from(len: u32) -> Result { - if len <= Self::MAX.0 { - Ok(Length(len)) - } else { - Err(ErrorKind::Overflow.into()) - } - } -} - -impl TryFrom for Length { - type Error = Error; - - fn try_from(len: usize) -> Result { - u32::try_from(len) - .map_err(|_| ErrorKind::Overflow)? - .try_into() - } -} - -impl TryFrom for usize { - type Error = Error; - - fn try_from(len: Length) -> Result { - len.0.try_into().map_err(|_| ErrorKind::Overflow.into()) - } -} - -impl Decodable<'_> for Length { - fn decode(decoder: &mut Decoder<'_>) -> Result { - match decoder.byte()? { - // Note: per X.690 Section 8.1.3.6.1 the byte 0x80 encodes indefinite - // lengths, which are not allowed in DER, so disallow that byte. - len if len < 0x80 => Ok(len.into()), - tag @ 0x81..=0x83 => { - let nbytes = tag.checked_sub(0x80).ok_or(ErrorKind::Overlength)? as usize; - let mut decoded_len = 0; - - for _ in 0..nbytes { - decoded_len = (decoded_len << 8) | decoder.byte()? as u32; - } - - let length = Length::try_from(decoded_len)?; - - // X.690 Section 10.1: DER lengths must be encoded with a minimum - // number of octets - if length.initial_octet() == Some(tag) { - Ok(length) - } else { - Err(ErrorKind::Overlength.into()) - } - } - _ => { - // We specialize to a maximum 4-byte length (including initial octet) - Err(ErrorKind::Overlength.into()) - } - } - } -} - -impl Encodable for Length { - fn encoded_len(&self) -> Result { - match self.0 { - 0..=0x7F => Ok(Length(1)), - 0x80..=0xFF => Ok(Length(2)), - 0x100..=0xFFFF => Ok(Length(3)), - 0x10000..=MAX_U32 => Ok(Length(4)), - _ => Err(ErrorKind::Overflow.into()), - } - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - if let Some(tag_byte) = self.initial_octet() { - encoder.byte(tag_byte)?; - - match self.0.to_be_bytes() { - [0, 0, 0, byte] => encoder.byte(byte), - [0, 0, bytes @ ..] => encoder.bytes(&bytes), - [0, bytes @ ..] => encoder.bytes(&bytes), - _ => Err(ErrorKind::Overlength.into()), - } - } else { - encoder.byte(self.0 as u8) - } - } -} - -impl fmt::Display for Length { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -#[cfg(test)] -mod tests { - use super::Length; - use crate::{Decodable, Encodable, ErrorKind}; - use core::convert::TryFrom; - - #[test] - fn decode() { - assert_eq!(Length::ZERO, Length::from_der(&[0x00]).unwrap()); - - assert_eq!(Length::from(0x7Fu8), Length::from_der(&[0x7F]).unwrap()); - - assert_eq!( - Length::from(0x80u8), - Length::from_der(&[0x81, 0x80]).unwrap() - ); - - assert_eq!( - Length::from(0xFFu8), - Length::from_der(&[0x81, 0xFF]).unwrap() - ); - - assert_eq!( - Length::from(0x100u16), - Length::from_der(&[0x82, 0x01, 0x00]).unwrap() - ); - - assert_eq!( - Length::try_from(0x10000u32).unwrap(), - Length::from_der(&[0x83, 0x01, 0x00, 0x00]).unwrap() - ); - } - - #[test] - fn encode() { - let mut buffer = [0u8; 4]; - - assert_eq!(&[0x00], Length::ZERO.encode_to_slice(&mut buffer).unwrap()); - - assert_eq!( - &[0x7F], - Length::from(0x7Fu8).encode_to_slice(&mut buffer).unwrap() - ); - - assert_eq!( - &[0x81, 0x80], - Length::from(0x80u8).encode_to_slice(&mut buffer).unwrap() - ); - - assert_eq!( - &[0x81, 0xFF], - Length::from(0xFFu8).encode_to_slice(&mut buffer).unwrap() - ); - - assert_eq!( - &[0x82, 0x01, 0x00], - Length::from(0x100u16).encode_to_slice(&mut buffer).unwrap() - ); - - assert_eq!( - &[0x83, 0x01, 0x00, 0x00], - Length::try_from(0x10000u32) - .unwrap() - .encode_to_slice(&mut buffer) - .unwrap() - ); - } - - #[test] - fn reject_indefinite_lengths() { - assert!(Length::from_der(&[0x80]).is_err()); - } - - #[test] - fn add_overflows_when_max_length_exceeded() { - let result = Length::MAX + Length::ONE; - assert_eq!( - result.err().map(|err| err.kind()), - Some(ErrorKind::Overflow) - ); - } -} diff --git a/der/src/lib.rs b/der/src/lib.rs deleted file mode 100644 index ed0f38ba..00000000 --- a/der/src/lib.rs +++ /dev/null @@ -1,397 +0,0 @@ -//! Pure Rust embedded-friendly implementation of the Distinguished Encoding Rules (DER) -//! for Abstract Syntax Notation One (ASN.1) as described in ITU [X.690]. -//! -//! # About -//! -//! This crate provides a `no_std`-friendly implementation of a subset of ASN.1 -//! DER necessary for decoding/encoding various cryptography-related formats -//! implemented as part of the [RustCrypto] project, e.g. the [`pkcs5`] and -//! [`pkcs8`] crates. -//! -//! The core implementation avoids any heap usage (with convenience methods -//! that allocate gated under the off-by-default `alloc` feature). -//! -//! # Minimum Supported Rust Version -//! -//! This crate requires **Rust 1.51** at a minimum. -//! -//! We may change the MSRV in the future, but it will be accompanied by a minor -//! version bump. -//! -//! # Usage -//! -//! ## [`Decodable`] and [`Encodable`] traits -//! -//! The [`Decodable`] and [`Encodable`] traits are the core abstractions on -//! which this crate is built and control what types can be (de)serialized -//! as ASN.1 DER. -//! -//! The traits are impl'd for the following Rust core types: -//! -//! - `()`: ASN.1 `NULL` (see also [`Null`]) -//! - [`bool`]: ASN.1 `BOOLEAN` -//! - [`i8`], [`i16`], [`i32`], [`i64`], [`i128`]: ASN.1 `INTEGER` -//! - [`u8`], [`u16`], [`u32`], [`u64`], [`u128`]: ASN.1 `INTEGER` -//! - [`str`], [`String`][`alloc::string::String`]: ASN.1 `UTF8String` -//! (see also [`Utf8String`]. `String` requires `alloc` feature) -//! - [`BTreeSet`][`alloc::collections::BTreeSet`]: ASN.1 `SET OF` (requires `alloc` feature) -//! - [`Option`]: ASN.1 `OPTIONAL` -//! - [`SystemTime`][`std::time::SystemTime`]: ASN.1 `GeneralizedTime` (requires `std` feature) -//! -//! The following ASN.1 types provided by this crate also impl these traits: -//! -//! - [`Any`]: ASN.1 `ANY` -//! - [`BitString`]: ASN.1 `BIT STRING` -//! - [`GeneralizedTime`]: ASN.1 `GeneralizedTime` -//! - [`Ia5String`]: ASN.1 `IA5String` -//! - [`Null`]: ASN.1 `NULL` -//! - [`ObjectIdentifier`]: ASN.1 `OBJECT IDENTIFIER` -//! - [`OctetString`]: ASN.1 `OCTET STRING` -//! - [`PrintableString`]: ASN.1 `PrintableString` (ASCII subset) -//! - [`Sequence`]: ASN.1 `SEQUENCE` -//! - [`SetOfRef`]: ASN.1 `SET OF` -//! - [`UIntBytes`]: ASN.1 unsigned `INTEGER` with raw access to encoded bytes -//! - [`UtcTime`]: ASN.1 `UTCTime` -//! - [`Utf8String`]: ASN.1 `UTF8String` -//! -//! ## Example -//! -//! The following example implements X.509's `AlgorithmIdentifier` message type -//! as defined in [RFC 5280 Section 4.1.1.2]. -//! -//! The ASN.1 schema for this message type is as follows: -//! -//! ```text -//! AlgorithmIdentifier ::= SEQUENCE { -//! algorithm OBJECT IDENTIFIER, -//! parameters ANY DEFINED BY algorithm OPTIONAL } -//! ``` -//! -//! Structured ASN.1 messages are typically encoded as a `SEQUENCE`, which -//! this crate maps to a Rust struct using the [`Message`] trait. This -//! trait is bounded on the [`Decodable`] trait and provides a blanket impl -//! of the [`Encodable`] trait, so any type which impls [`Message`] can be -//! used for both decoding and encoding. -//! -//! The [`Decoder`] and [`Encoder`] types provide the decoding/encoding API -//! respectively, and are designed to work in conjunction with concrete ASN.1 -//! types which impl the [`Decodable`] and [`Encodable`] traits, including -//! all types which impl the [`Message`] trait. -//! -//! The following code example shows how to define a struct which maps to the -//! above schema, as well as impl the [`Message`] trait for that struct: -//! -//! ``` -//! # #[cfg(all(feature = "alloc", feature = "oid"))] -//! # { -//! // Note: the following example does not require the `std` feature at all. -//! // It does leverage the `alloc` feature, but also provides instructions for -//! // "heapless" usage when the `alloc` feature is disabled. -//! use core::convert::{TryFrom, TryInto}; -//! use der::{ -//! asn1::{Any, ObjectIdentifier}, -//! Decodable, Encodable, Message -//! }; -//! -//! /// X.509 `AlgorithmIdentifier`. -//! #[derive(Copy, Clone, Debug, Eq, PartialEq)] -//! pub struct AlgorithmIdentifier<'a> { -//! /// This field contains an ASN.1 `OBJECT IDENTIFIER`, a.k.a. OID. -//! pub algorithm: ObjectIdentifier, -//! -//! /// This field is `OPTIONAL` and contains the ASN.1 `ANY` type, which -//! /// in this example allows arbitrary algorithm-defined parameters. -//! pub parameters: Option> -//! } -//! -//! // Note: types which impl `TryFrom, Error = der::Error>` receive a -//! // blanket impl of the `Decodable` trait, therefore satisfying the -//! // `Decodable` trait bounds on `Message`, which is impl'd below. -//! impl<'a> TryFrom> for AlgorithmIdentifier<'a> { -//! type Error = der::Error; -//! -//! fn try_from(any: Any<'a>) -> der::Result { -//! // The `Any::sequence` method asserts that an `Any` value -//! // contains an ASN.1 `SEQUENCE` then calls the provided `FnOnce` -//! // with a `der::Decoder` which can be used to decode it. -//! any.sequence(|decoder| { -//! // The `der::Decoder::Decode` method can be used to decode any -//! // type which impls the `Decodable` trait, which is impl'd for -//! // all of the ASN.1 built-in types in the `der` crate. -//! // -//! // Note that if your struct's fields don't contain an ASN.1 -//! // built-in type specifically, there are also helper methods -//! // for all of the built-in types supported by this library -//! // which can be used to select a specific type. -//! // -//! // For example, another way of decoding this particular field, -//! // which contains an ASN.1 `OBJECT IDENTIFIER`, is by calling -//! // `decoder.oid()`. Similar methods are defined for other -//! // ASN.1 built-in types. -//! let algorithm = decoder.decode()?; -//! -//! // This field contains an ASN.1 `OPTIONAL` type. The `der` crate -//! // maps this directly to Rust's `Option` type and provides -//! // impls of the `Decodable` and `Encodable` traits for `Option`. -//! // To explicitly request an `OPTIONAL` type be decoded, use the -//! // `decoder.optional()` method. -//! let parameters = decoder.decode()?; -//! -//! // The value returned from the provided `FnOnce` will be -//! // returned from the `any.sequence(...)` call above. -//! // Note that the entire sequence body *MUST* be consumed -//! // or an error will be returned. -//! Ok(Self { algorithm, parameters }) -//! }) -//! } -//! } -//! -//! impl<'a> Message<'a> for AlgorithmIdentifier<'a> { -//! // The `Message::fields` method is used for encoding and functions as -//! // a visitor for all of the fields in a message. -//! // -//! // To implement it, you must define a slice containing `Encodable` -//! // trait objects, then pass it to the provided `field_encoder` -//! // function, which is implemented by the `der` crate and handles -//! // message serialization. -//! // -//! // Trait objects are used because they allow for slices containing -//! // heterogeneous field types, and a callback is used to allow for the -//! // construction of temporary field encoder types. The latter means -//! // that the fields of your Rust struct don't necessarily need to -//! // impl the `Encodable` trait, but if they don't you must construct -//! // a temporary wrapper value which does. -//! // -//! // Types which impl the `Message` trait receive blanket impls of both -//! // the `Encodable` and `Tagged` traits (where the latter is impl'd as -//! // `Tagged::TAG = der::Tag::Sequence`. -//! fn fields(&self, field_encoder: F) -> der::Result -//! where -//! F: FnOnce(&[&dyn Encodable]) -> der::Result, -//! { -//! field_encoder(&[&self.algorithm, &self.parameters]) -//! } -//! } -//! -//! // Example parameters value: OID for the NIST P-256 elliptic curve. -//! let parameters = "1.2.840.10045.3.1.7".parse::().unwrap(); -//! -//! // We need to convert `parameters` into an `Any<'a>` type, which wraps a -//! // `&'a [u8]` byte slice. -//! // -//! // To do that, we need owned DER-encoded data so that we can have -//! // `Any` borrow a reference to it, so we have to serialize the OID. -//! // -//! // When the `alloc` feature of this crate is enabled, any type that impls -//! // the `Encodable` trait including all ASN.1 built-in types and any type -//! // which impls `Message` can be serialized by calling `Encodable::to_vec()`. -//! // -//! // If you would prefer to avoid allocations, you can create a byte array -//! // as backing storage instead, pass that to `der::Encoder::new`, and then -//! // encode the `parameters` value using `encoder.encode(parameters)`. -//! let der_encoded_parameters = parameters.to_vec().unwrap(); -//! -//! let algorithm_identifier = AlgorithmIdentifier { -//! // OID for `id-ecPublicKey`, if you're curious -//! algorithm: "1.2.840.10045.2.1".parse().unwrap(), -//! -//! // `Any<'a>` impls `TryFrom<&'a [u8]>`, which parses the provided -//! // slice as an ASN.1 DER-encoded message. -//! parameters: Some(der_encoded_parameters.as_slice().try_into().unwrap()) -//! }; -//! -//! // Serialize the `AlgorithmIdentifier` created above as ASN.1 DER, -//! // allocating a `Vec` for storage. -//! // -//! // As mentioned earlier, if you don't have the `alloc` feature enabled you -//! // can create a fix-sized array instead, then call `Encoder::new` with a -//! // reference to it, then encode the message using -//! // `encoder.encode(algorithm_identifier)`, then finally `encoder.finish()` -//! // to obtain a byte slice containing the encoded message. -//! let der_encoded_algorithm_identifier = algorithm_identifier.to_vec().unwrap(); -//! -//! // Deserialize the `AlgorithmIdentifier` we just serialized from ASN.1 DER -//! // using `der::Decodable::from_bytes`. -//! let decoded_algorithm_identifier = AlgorithmIdentifier::from_der( -//! &der_encoded_algorithm_identifier -//! ).unwrap(); -//! -//! // Ensure the original `AlgorithmIdentifier` is the same as the one we just -//! // decoded from ASN.1 DER. -//! assert_eq!(algorithm_identifier, decoded_algorithm_identifier); -//! # } -//! ``` -//! -//! ## Custom derive support -//! -//! When the `derive` feature of this crate is enabled, the following custom -//! derive macros are available: -//! -//! - [`Choice`]: derive for `CHOICE` enum (see [`der_derive::Choice`]) -//! - [`Message`]: derive for `SEQUENCE` struct (see [`der_derive::Message`]) -//! -//! ### Derive [`Message`] for `SEQUENCE` struct -//! -//! The following is a code example of how to use the [`Message`] custom derive: -//! -//! ``` -//! # #[cfg(all(feature = "alloc", feature = "derive", feature = "oid"))] -//! # { -//! use der::{asn1::{Any, ObjectIdentifier}, Encodable, Decodable, Message}; -//! use core::convert::TryInto; -//! -//! /// X.509 `AlgorithmIdentifier` (same as above) -//! #[derive(Copy, Clone, Debug, Eq, PartialEq, Message)] // NOTE: added `Message` -//! pub struct AlgorithmIdentifier<'a> { -//! /// This field contains an ASN.1 `OBJECT IDENTIFIER`, a.k.a. OID. -//! pub algorithm: ObjectIdentifier, -//! -//! /// This field is `OPTIONAL` and contains the ASN.1 `ANY` type, which -//! /// in this example allows arbitrary algorithm-defined parameters. -//! pub parameters: Option> -//! } -//! -//! // Example parameters value: OID for the NIST P-256 elliptic curve. -//! let parameters = "1.2.840.10045.3.1.7".parse::().unwrap(); -//! let der_encoded_parameters = parameters.to_vec().unwrap(); -//! -//! let algorithm_identifier = AlgorithmIdentifier { -//! // OID for `id-ecPublicKey`, if you're curious -//! algorithm: "1.2.840.10045.2.1".parse().unwrap(), -//! -//! // `Any<'a>` impls `TryFrom<&'a [u8]>`, which parses the provided -//! // slice as an ASN.1 DER-encoded message. -//! parameters: Some(der_encoded_parameters.as_slice().try_into().unwrap()) -//! }; -//! -//! // Encode -//! let der_encoded_algorithm_identifier = algorithm_identifier.to_vec().unwrap(); -//! -//! // Decode -//! let decoded_algorithm_identifier = AlgorithmIdentifier::from_der( -//! &der_encoded_algorithm_identifier -//! ).unwrap(); -//! -//! assert_eq!(algorithm_identifier, decoded_algorithm_identifier); -//! # } -//! ``` -//! -//! For fields which don't directly impl [`Decodable`] and [`Encodable`], -//! you can add annotations to convert to an intermediate ASN.1 type -//! first, so long as that type impls `TryFrom` and `Into` for the -//! ASN.1 type. -//! -//! For example, structs containing `&'a [u8]` fields may want them encoded -//! as either a `BIT STRING` or `OCTET STRING`. By using the -//! `#[asn1(type = "BIT STRING")]` annotation it's possible to select which -//! ASN.1 type should be used. -//! -//! Building off the above example: -//! -//! ```rust -//! # #[cfg(all(feature = "alloc", feature = "derive", feature = "oid"))] -//! # { -//! # use der::{asn1::{Any, ObjectIdentifier}, Message}; -//! # -//! # #[derive(Copy, Clone, Debug, Eq, PartialEq, Message)] -//! # pub struct AlgorithmIdentifier<'a> { -//! # pub algorithm: ObjectIdentifier, -//! # pub parameters: Option> -//! # } -//! /// X.509 `SubjectPublicKeyInfo` (SPKI) -//! #[derive(Copy, Clone, Debug, Eq, PartialEq, Message)] -//! pub struct SubjectPublicKeyInfo<'a> { -//! /// X.509 `AlgorithmIdentifier` -//! pub algorithm: AlgorithmIdentifier<'a>, -//! -//! /// Public key data -//! #[asn1(type = "BIT STRING")] -//! pub subject_public_key: &'a [u8], -//! } -//! # } -//! ``` -//! -//! # See also -//! -//! For more information about ASN.1 DER we recommend the following guides: -//! -//! - [A Layman's Guide to a Subset of ASN.1, BER, and DER] (RSA Laboratories) -//! - [A Warm Welcome to ASN.1 and DER] (Let's Encrypt) -//! -//! [X.690]: https://www.itu.int/rec/T-REC-X.690/ -//! [RustCrypto]: https://github.com/rustcrypto -//! [`pkcs5`]: https://docs.rs/pkcs5/ -//! [`pkcs8`]: https://docs.rs/pkcs8/ -//! [RustCrypto/utils#370]: https://github.com/RustCrypto/utils/issues/370 -//! [RFC 5280 Section 4.1.1.2]: https://tools.ietf.org/html/rfc5280#section-4.1.1.2 -//! [A Layman's Guide to a Subset of ASN.1, BER, and DER]: https://luca.ntop.org/Teaching/Appunti/asn1.html -//! [A Warm Welcome to ASN.1 and DER]: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/ -//! -//! [`Any`]: asn1::Any -//! [`UIntBytes`]: asn1::UIntBytes -//! [`BitString`]: asn1::BitString -//! [`GeneralizedTime`]: asn1::GeneralizedTime -//! [`Ia5String`]: asn1::Ia5String -//! [`Null`]: asn1::Null -//! [`ObjectIdentifier`]: asn1::ObjectIdentifier -//! [`OctetString`]: asn1::OctetString -//! [`PrintableString`]: asn1::PrintableString -//! [`Sequence`]: asn1::Sequence -//! [`SetOfRef`]: asn1::SetOfRef -//! [`UtcTime`]: asn1::UtcTime -//! [`Utf8String`]: asn1::Utf8String - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/der/0.4.1" -)] -#![forbid(unsafe_code, clippy::unwrap_used)] -#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] - -#[cfg(feature = "alloc")] -extern crate alloc; - -#[cfg(feature = "std")] -extern crate std; - -pub mod asn1; -pub mod message; - -mod byte_slice; -mod choice; -mod datetime; -mod decodable; -mod decoder; -mod encodable; -mod encoder; -mod error; -mod header; -mod length; -mod str_slice; -mod tag; - -pub use crate::{ - choice::Choice, - decodable::Decodable, - decoder::Decoder, - encodable::Encodable, - encoder::Encoder, - error::{Error, ErrorKind, Result}, - header::Header, - length::Length, - message::Message, - tag::{Class, Tag, TagNumber, Tagged}, -}; - -#[cfg(feature = "bigint")] -#[cfg_attr(docsrs, doc(cfg(feature = "bigint")))] -pub use crypto_bigint as bigint; - -#[cfg(feature = "derive")] -#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] -pub use der_derive::{Choice, Message}; - -pub(crate) use crate::byte_slice::ByteSlice; diff --git a/der/src/message.rs b/der/src/message.rs deleted file mode 100644 index 16df4cb9..00000000 --- a/der/src/message.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! The [`Message`] trait simplifies writing decoders/encoders which map ASN.1 -//! `SEQUENCE`s to Rust structs. - -use crate::{Decodable, Encodable, Encoder, Header, Length, Result, Tag, Tagged}; - -/// Messages encoded as an ASN.1 `SEQUENCE`. -/// -/// The "message" pattern this trait provides is not an ASN.1 concept, -/// but rather a pattern for writing ASN.1 DER decoders and encoders which -/// map ASN.1 `SEQUENCE` types to Rust structs with a minimum of code. -/// -/// Types which impl this trait receive blanket impls for the [`Decodable`], -/// [`Encodable`], and [`Tagged`] traits. -pub trait Message<'a>: Decodable<'a> { - /// Call the provided function with a slice of [`Encodable`] trait objects - /// representing the fields of this message. - /// - /// This method uses a callback because structs with fields which aren't - /// directly [`Encodable`] may need to construct temporary values from - /// their fields prior to encoding. - fn fields(&self, f: F) -> Result - where - F: FnOnce(&[&dyn Encodable]) -> Result; -} - -impl<'a, M> Encodable for M -where - M: Message<'a>, -{ - fn encoded_len(&self) -> Result { - self.fields(|fields| { - let inner_len = encoded_len_inner(fields)?; - Header::new(Tag::Sequence, inner_len)?.encoded_len() + inner_len - }) - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - self.fields(|fields| encoder.message(fields)) - } -} - -impl<'a, M> Tagged for M -where - M: Message<'a>, -{ - const TAG: Tag = Tag::Sequence; -} - -/// Obtain the length of an ASN.1 `SEQUENCE` consisting of the given -/// [`Encodable`] fields when serialized as ASN.1 DER, including the header -/// (i.e. tag and length) -pub fn encoded_len(fields: &[&dyn Encodable]) -> Result { - let inner_len = encoded_len_inner(fields)?; - Header::new(Tag::Sequence, inner_len)?.encoded_len() + inner_len -} - -/// Obtain the length of an ASN.1 message `SEQUENCE` consisting of the given -/// [`Encodable`] fields when serialized as ASN.1 DER, including the header -/// (i.e. tag and length) -pub(crate) fn encoded_len_inner(fields: &[&dyn Encodable]) -> Result { - fields.iter().fold(Ok(Length::ZERO), |sum, encodable| { - sum + encodable.encoded_len()? - }) -} diff --git a/der/src/str_slice.rs b/der/src/str_slice.rs deleted file mode 100644 index c375560d..00000000 --- a/der/src/str_slice.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Common handling for types backed by `str` slices with enforcement of a -//! library-level length limitation i.e. `Length::max()`. - -use crate::{Length, Result}; -use core::{convert::TryFrom, str}; - -/// String slice newtype which respects the [`Length::max`] limit. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub(crate) struct StrSlice<'a> { - /// Inner value - pub(crate) inner: &'a str, - - /// Precomputed `Length` (avoids possible panicking conversions) - pub(crate) length: Length, -} - -impl<'a> StrSlice<'a> { - /// Create a new [`StrSlice`], ensuring that the byte representation of - /// the provided `str` value is shorter than `Length::max()`. - pub fn new(s: &'a str) -> Result { - Ok(Self { - inner: s, - length: Length::try_from(s.as_bytes().len())?, - }) - } - - /// Parse a [`StrSlice`] from UTF-8 encoded bytes. - pub fn from_bytes(bytes: &'a [u8]) -> Result { - Self::new(str::from_utf8(bytes)?) - } - - /// Borrow the inner `str` - pub fn as_str(&self) -> &'a str { - self.inner - } - - /// Borrow the inner byte slice - pub fn as_bytes(&self) -> &'a [u8] { - self.inner.as_bytes() - } - - /// Get the [`Length`] of this [`StrSlice`] - pub fn len(self) -> Length { - self.length - } - - /// Is this [`StrSlice`] empty? - pub fn is_empty(self) -> bool { - self.len() == Length::ZERO - } -} - -impl AsRef for StrSlice<'_> { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl AsRef<[u8]> for StrSlice<'_> { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} diff --git a/der/src/tag.rs b/der/src/tag.rs deleted file mode 100644 index 41f746d2..00000000 --- a/der/src/tag.rs +++ /dev/null @@ -1,262 +0,0 @@ -//! ASN.1 tags. - -mod class; -mod number; - -pub use self::{class::Class, number::TagNumber}; - -use crate::{Decodable, Decoder, Encodable, Encoder, Error, ErrorKind, Length, Result}; -use core::{convert::TryFrom, fmt}; - -/// Indicator bit for constructed form encoding (i.e. vs primitive form) -const CONSTRUCTED_FLAG: u8 = 0b100000; - -/// Types with an associated ASN.1 [`Tag`]. -pub trait Tagged { - /// ASN.1 tag - const TAG: Tag; -} - -/// ASN.1 tags. -/// -/// Tags are the leading identifier octet of the Tag-Length-Value encoding -/// used by ASN.1 DER and identify the type of the subsequent value. -/// -/// They are described in X.690 Section 8.1.2: Identifier octets, and -/// structured as follows: -/// -/// ```text -/// | Class | P/C | Tag Number | -/// ``` -/// -/// - Bits 8/7: [`Class`] -/// - Bit 6: primitive (0) or constructed (1) -/// - Bits 5-1: tag number -#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] -#[non_exhaustive] -pub enum Tag { - /// `BOOLEAN` tag: 0x01 - Boolean, - - /// `INTEGER` tag: 0x02 - Integer, - - /// `BIT STRING` tag: 0x03 - BitString, - - /// `OCTET STRING` tag: 0x04 - OctetString, - - /// `NULL` tag: 0x05 - Null, - - /// `OBJECT IDENTIFIER` tag: 0x06 - ObjectIdentifier, - - /// `UTF8String` tag: 0x0C - Utf8String, - - /// `SEQUENCE` tag: 0x10 - Sequence, - - /// `SET` and `SET OF` tag: 0x11 - Set, - - /// `PrintableString` tag: 0x13 - PrintableString, - - /// `IA5String` tag: 0x16 - Ia5String, - - /// `UTCTime` tag: 0x17 - UtcTime, - - /// `GeneralizedTime` tag: 0x18 - GeneralizedTime, - - /// Application tag. - Application(TagNumber), - - /// Context-specific tag. - ContextSpecific(TagNumber), - - /// Private tag number. - Private(TagNumber), -} - -impl Tag { - /// Assert that this [`Tag`] matches the provided expected tag. - /// - /// On mismatch, returns an [`Error`] with [`ErrorKind::UnexpectedTag`]. - pub fn assert_eq(self, expected: Tag) -> Result { - if self == expected { - Ok(self) - } else { - Err(ErrorKind::UnexpectedTag { - expected: Some(expected), - actual: self, - } - .into()) - } - } - - /// Get the [`Class`] that corresponds to this [`Tag`]. - pub fn class(self) -> Class { - match self { - Tag::Application(_) => Class::Application, - Tag::ContextSpecific(_) => Class::ContextSpecific, - Tag::Private(_) => Class::Private, - _ => Class::Universal, - } - } - - /// Get the octet encoding for this [`Tag`]. - pub fn octet(self) -> u8 { - match self { - Tag::Boolean => 0x01, - Tag::Integer => 0x02, - Tag::BitString => 0x03, - Tag::OctetString => 0x04, - Tag::Null => 0x05, - Tag::ObjectIdentifier => 0x06, - Tag::Utf8String => 0x0C, - Tag::Sequence => 0x10 | CONSTRUCTED_FLAG, - Tag::Set => 0x11 | CONSTRUCTED_FLAG, - Tag::PrintableString => 0x13, - Tag::Ia5String => 0x16, - Tag::UtcTime => 0x17, - Tag::GeneralizedTime => 0x18, - Tag::Application(number) | Tag::ContextSpecific(number) | Tag::Private(number) => { - self.class().octet(number, true) - } - } - } - - /// Create an [`Error`] for an non-canonical value with the ASN.1 type - /// identified by this tag. - pub fn non_canonical_error(self) -> Error { - ErrorKind::Value { tag: self }.into() - } - - /// Create an [`Error`] for an invalid value with the ASN.1 type identified - /// by this tag. - pub fn value_error(self) -> Error { - ErrorKind::Value { tag: self }.into() - } -} - -impl TryFrom for Tag { - type Error = Error; - - fn try_from(byte: u8) -> Result { - match byte { - 0x01 => Ok(Tag::Boolean), - 0x02 => Ok(Tag::Integer), - 0x03 => Ok(Tag::BitString), - 0x04 => Ok(Tag::OctetString), - 0x05 => Ok(Tag::Null), - 0x06 => Ok(Tag::ObjectIdentifier), - 0x0C => Ok(Tag::Utf8String), - 0x13 => Ok(Tag::PrintableString), - 0x16 => Ok(Tag::Ia5String), - 0x17 => Ok(Tag::UtcTime), - 0x18 => Ok(Tag::GeneralizedTime), - 0x30 => Ok(Tag::Sequence), // constructed - 0x31 => Ok(Tag::Set), // constructed - 0x60..=0x7E => Ok(Tag::Application(TagNumber(byte & 0b11111))), // constructed - 0xA0..=0xBE => Ok(Tag::ContextSpecific(TagNumber(byte & 0b11111))), // constructed - 0xE0..=0xFE => Ok(Tag::Private(TagNumber(byte & 0b11111))), // constructed - _ => Err(ErrorKind::UnknownTag { byte }.into()), - } - } -} - -impl From for u8 { - fn from(tag: Tag) -> u8 { - tag.octet() - } -} - -impl From<&Tag> for u8 { - fn from(tag: &Tag) -> u8 { - u8::from(*tag) - } -} - -impl Decodable<'_> for Tag { - fn decode(decoder: &mut Decoder<'_>) -> Result { - decoder.byte().and_then(Self::try_from) - } -} - -impl Encodable for Tag { - fn encoded_len(&self) -> Result { - Ok(Length::ONE) - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - encoder.byte(self.into()) - } -} - -impl fmt::Display for Tag { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Tag::Boolean => f.write_str("BOOLEAN"), - Tag::Integer => f.write_str("INTEGER"), - Tag::BitString => f.write_str("BIT STRING"), - Tag::OctetString => f.write_str("OCTET STRING"), - Tag::Null => f.write_str("NULL"), - Tag::ObjectIdentifier => f.write_str("OBJECT IDENTIFIER"), - Tag::Utf8String => f.write_str("UTF8String"), - Tag::Set => f.write_str("SET"), - Tag::PrintableString => f.write_str("PrintableString"), - Tag::Ia5String => f.write_str("IA5String"), - Tag::UtcTime => f.write_str("UTCTime"), - Tag::GeneralizedTime => f.write_str("GeneralizedTime"), - Tag::Sequence => f.write_str("SEQUENCE"), - Tag::Application(n) => write!(f, "APPLICATION {}", n), - Tag::ContextSpecific(n) => write!(f, "CONTEXT-SPECIFIC {}", n), - Tag::Private(n) => write!(f, "PRIVATE {}", n), - } - } -} - -impl fmt::Debug for Tag { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Tag(0x{:02x}: {})", u8::from(*self), self) - } -} - -#[cfg(test)] -mod tests { - use super::TagNumber; - use super::{Class, Tag}; - - #[test] - fn tag_class() { - assert_eq!(Tag::Boolean.class(), Class::Universal); - assert_eq!(Tag::Integer.class(), Class::Universal); - assert_eq!(Tag::BitString.class(), Class::Universal); - assert_eq!(Tag::OctetString.class(), Class::Universal); - assert_eq!(Tag::Null.class(), Class::Universal); - assert_eq!(Tag::ObjectIdentifier.class(), Class::Universal); - assert_eq!(Tag::Utf8String.class(), Class::Universal); - assert_eq!(Tag::Set.class(), Class::Universal); - assert_eq!(Tag::PrintableString.class(), Class::Universal); - assert_eq!(Tag::Ia5String.class(), Class::Universal); - assert_eq!(Tag::UtcTime.class(), Class::Universal); - assert_eq!(Tag::GeneralizedTime.class(), Class::Universal); - assert_eq!(Tag::Sequence.class(), Class::Universal); - - for num in 0..=30 { - let tag_num = TagNumber::new(num); - assert_eq!(Tag::Application(tag_num).class(), Class::Application); - assert_eq!( - Tag::ContextSpecific(tag_num).class(), - Class::ContextSpecific - ); - assert_eq!(Tag::Private(tag_num).class(), Class::Private); - } - } -} diff --git a/der/src/tag/class.rs b/der/src/tag/class.rs deleted file mode 100644 index 4c2ccff9..00000000 --- a/der/src/tag/class.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Class of an ASN.1 tag. - -use super::{TagNumber, CONSTRUCTED_FLAG}; -use core::fmt; - -/// Class of an ASN.1 tag. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -#[repr(u8)] -pub enum Class { - /// `UNIVERSAL`: built-in types whose meaning is the same in all - /// applications. - Universal = 0b00000000, - - /// `APPLICATION`: types whose meaning is specific to an application, - /// - /// Types in two different applications may have the same - /// application-specific tag and different meanings. - Application = 0b01000000, - - /// `CONTEXT-SPECIFIC`: types whose meaning is specific to a given - /// structured type. - /// - /// Context-specific tags are used to distinguish between component types - /// with the same underlying tag within the context of a given structured - /// type, and component types in two different structured types may have - /// the same tag and different meanings. - ContextSpecific = 0b10000000, - - /// `PRIVATE`: types whose meaning is specific to a given enterprise. - Private = 0b11000000, -} - -impl Class { - /// Compute the identifier octet for a tag number of this class. - pub(super) fn octet(self, number: TagNumber, constructed: bool) -> u8 { - self as u8 | number.value() | (constructed as u8 * CONSTRUCTED_FLAG) - } -} - -impl fmt::Display for Class { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Class::Universal => "UNIVERSAL", - Class::Application => "APPLICATION", - Class::ContextSpecific => "CONTEXT-SPECIFIC", - Class::Private => "PRIVATE", - }) - } -} diff --git a/der/src/tag/number.rs b/der/src/tag/number.rs deleted file mode 100644 index dc4ba1fd..00000000 --- a/der/src/tag/number.rs +++ /dev/null @@ -1,81 +0,0 @@ -//! ASN.1 tag numbers - -use super::Tag; -use crate::{Error, ErrorKind, Result}; -use core::{convert::TryFrom, fmt}; - -/// ASN.1 tag numbers (i.e. lower 5 bits of a [`Tag`]). -/// -/// From X.690 Section 8.1.2.2: -/// -/// > bits 5 to 1 shall encode the number of the tag as a binary integer with -/// > bit 5 as the most significant bit. -/// -/// This library supports tag numbers ranging from zero to 30 (inclusive), -/// which can be represented as a single identifier octet. -/// -/// Section 8.1.2.4 describes how to support multi-byte tag numbers, which are -/// encoded by using a leading tag number of 31 (`0b11111`). This library -/// deliberately does not support this: tag numbers greater than 30 are -/// disallowed. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct TagNumber(pub(super) u8); - -impl TagNumber { - /// Maximum tag number supported (inclusive). - pub const MAX: u8 = 30; - - /// Create a new tag number (const-friendly). - /// - /// Panics if the tag number is greater than [`TagNumber::MAX`]. For a fallible - /// conversion, use [`TryFrom`] instead. - #[allow(clippy::no_effect)] - pub const fn new(byte: u8) -> Self { - // TODO(tarcieri): hax! use const panic when available - ["tag number out of range"][(byte > Self::MAX) as usize]; - Self(byte) - } - - /// Create an `APPLICATION` tag with this tag number. - pub fn application(self) -> Tag { - Tag::Application(self) - } - - /// Create a `CONTEXT-SPECIFIC` tag with this tag number. - pub fn context_specific(self) -> Tag { - Tag::ContextSpecific(self) - } - - /// Create a `PRIVATE` tag with this tag number. - pub fn private(self) -> Tag { - Tag::Private(self) - } - - /// Get the inner value. - pub fn value(self) -> u8 { - self.0 - } -} - -impl TryFrom for TagNumber { - type Error = Error; - - fn try_from(byte: u8) -> Result { - match byte { - 0..=Self::MAX => Ok(Self(byte)), - _ => Err(ErrorKind::UnknownTag { byte }.into()), - } - } -} - -impl From for u8 { - fn from(tag_number: TagNumber) -> u8 { - tag_number.0 - } -} - -impl fmt::Display for TagNumber { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/der/tests/derive.rs b/der/tests/derive.rs deleted file mode 100644 index 53cf62c4..00000000 --- a/der/tests/derive.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Tests for custom derive support -// TODO(tarcieri): test all types supported by `der_derive` - -#![cfg(feature = "derive")] - -use der::{ - asn1::{GeneralizedTime, UtcTime}, - Choice, Decodable, Encodable, Encoder, -}; -use hex_literal::hex; -use std::time::Duration; - -/// Custom derive test case for the `Choice` mcaro. -/// -/// Based on `Time` as defined in RFC 5280: -/// -/// -/// ```text -/// Time ::= CHOICE { -/// utcTime UTCTime, -/// generalTime GeneralizedTime } -/// ``` -#[derive(Choice)] -pub enum Time { - #[asn1(type = "UTCTime")] - UtcTime(UtcTime), - - #[asn1(type = "GeneralizedTime")] - GeneralTime(GeneralizedTime), -} - -impl Time { - fn unix_duration(self) -> Duration { - match self { - Time::UtcTime(t) => t.unix_duration(), - Time::GeneralTime(t) => t.unix_duration(), - } - } -} - -const UTC_TIMESTAMP: &[u8] = &hex!("17 0d 39 31 30 35 30 36 32 33 34 35 34 30 5a"); -const GENERAL_TIMESTAMP: &[u8] = &hex!("18 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a"); - -#[test] -fn decode_enum_variants() { - let utc_time = Time::from_der(UTC_TIMESTAMP).unwrap(); - assert_eq!(utc_time.unix_duration().as_secs(), 673573540); - - let general_time = Time::from_der(GENERAL_TIMESTAMP).unwrap(); - assert_eq!(general_time.unix_duration().as_secs(), 673573540); -} - -#[test] -fn encode_enum_variants() { - let mut buf = [0u8; 128]; - - let utc_time = Time::from_der(UTC_TIMESTAMP).unwrap(); - let mut encoder = Encoder::new(&mut buf); - utc_time.encode(&mut encoder).unwrap(); - assert_eq!(UTC_TIMESTAMP, encoder.finish().unwrap()); - - let general_time = Time::from_der(GENERAL_TIMESTAMP).unwrap(); - let mut encoder = Encoder::new(&mut buf); - general_time.encode(&mut encoder).unwrap(); - assert_eq!(GENERAL_TIMESTAMP, encoder.finish().unwrap()); -} diff --git a/hex-literal/CHANGELOG.md b/hex-literal/CHANGELOG.md index 36375e7d..1c72f15b 100644 --- a/hex-literal/CHANGELOG.md +++ b/hex-literal/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.4 (2021-11-11) +### Changed +- Provide more info in `panic!` messages ([#664]) +- Minor changes in the comments filtration code ([#666]) + +### Added +- New tests for the `hex!()` macro and internal documentation ([#664]) + +### Fixed +- Make `hex!()` error when forward slash encountered as last byte ([#665]) + +[#664]: https://github.com/RustCrypto/utils/pull/664 +[#665]: https://github.com/RustCrypto/utils/pull/665 +[#666]: https://github.com/RustCrypto/utils/pull/666 + ## 0.3.3 (2021-07-17) ### Added - Accept sequence of string literals ([#519]) diff --git a/hex-literal/Cargo.toml b/hex-literal/Cargo.toml index ec884607..eb64b85b 100644 --- a/hex-literal/Cargo.toml +++ b/hex-literal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hex-literal" -version = "0.3.3" # Also update html_root_url in lib.rs when bumping this +version = "0.3.4" # Also update html_root_url in lib.rs when bumping this authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" description = "Procedural macro for converting hexadecimal string to byte array at compile time." diff --git a/hex-literal/src/comments.rs b/hex-literal/src/comments.rs index 02dd6993..6f8799d2 100644 --- a/hex-literal/src/comments.rs +++ b/hex-literal/src/comments.rs @@ -25,9 +25,10 @@ impl> Iterator for ExcludingComments { let next_byte = self.next_byte(); if next_byte.is_none() { match self.state { - State::BlockComment | State::PotentiallyLeavingBlockComment => { + State::BlockComment | State::PotentialBlockCommentEnd => { panic!("block comment not terminated with */") } + State::PotentialComment { .. } => panic!("encountered isolated `/`"), _ => {} } } @@ -43,16 +44,17 @@ impl> Iterator for ExcludingComments { /// '/' '*' /// LineComment BlockComment /// '\n' '*' -/// Normal PotentiallyLeavingBlockComment +/// Normal PotentialBlockCommentEnd /// '/' '_' /// Normal BlockComment -/// +/// +#[derive(Copy, Clone)] enum State { Normal, - PotentialComment { previous: u8 }, + PotentialComment, LineComment, BlockComment, - PotentiallyLeavingBlockComment, + PotentialBlockCommentEnd, } impl> ExcludingComments { @@ -65,81 +67,22 @@ impl> ExcludingComments { fn next_byte(&mut self) -> Option { loop { - return match self.state { - State::Normal => { - let next = self.iter.next()?; - match next { - b'/' => { - self.state = State::PotentialComment { previous: next }; - continue; - } - _ => Some(next), - } - } - State::PotentialComment { previous } => { - let peeked_next = self.iter.peek()?; - match peeked_next { - b'/' => { - // second /, enter line comment and consume - self.iter.next(); - self.state = State::LineComment; - continue; - } - b'*' => { - /* entering a block comment consume '*' */ - self.iter.next(); - self.state = State::BlockComment; - continue; - } - _ => { - // here we need to emit the previous character (the first '/') - // and do not consume the current character - self.state = State::Normal; - return Some(previous); - } - } - } - State::LineComment => { - let next = self.iter.next()?; - match next { - b'\n' => { - self.state = State::Normal; - return Some(next); - } - _ => { - // ignore all other characters while in the line comment - continue; - } - } - } - State::BlockComment => { - let next = self.iter.next()?; - match next { - b'*' => { - self.state = State::PotentiallyLeavingBlockComment; - continue; - } - _ => { - /* ignore all other characters while in the block comment */ - continue; - } - } - } - State::PotentiallyLeavingBlockComment => { - let next = self.iter.next()?; - match next { - b'/' => { - /* Left the block comment */ - self.state = State::Normal; - continue; - } - _ => { - /* we're still in the block comment */ - self.state = State::BlockComment; - continue; - } - } + let next = self.iter.next()?; + self.state = match (self.state, next) { + (State::Normal, b'/') => State::PotentialComment, + (State::Normal, _) => return Some(next), + (State::PotentialComment, b'/') => State::LineComment, + (State::PotentialComment, b'*') => State::BlockComment, + (State::PotentialComment, _) => panic!("encountered isolated `/`"), + (State::LineComment, b'\n') => { + self.state = State::Normal; + return Some(b'\n'); } + (State::LineComment, _) => continue, + (State::BlockComment, b'*') => State::PotentialBlockCommentEnd, + (State::BlockComment, _) => continue, + (State::PotentialBlockCommentEnd, b'/') => State::Normal, + (State::PotentialBlockCommentEnd, _) => State::BlockComment, }; } } @@ -193,8 +136,9 @@ mod tests { } #[test] - fn single_slash_is_not_excluded() { - assert_eq!(exclude_comments("ab/cd"), "ab/cd"); + #[should_panic] + fn panic_on_single_slash() { + exclude_comments("ab/cd"); } #[test] diff --git a/hex-literal/src/lib.rs b/hex-literal/src/lib.rs index 6608d102..afdc533f 100644 --- a/hex-literal/src/lib.rs +++ b/hex-literal/src/lib.rs @@ -47,9 +47,9 @@ //! # } //! ``` #![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/hex-literal/0.3.3" + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_root_url = "https://docs.rs/hex-literal/0.3.4" )] mod comments; @@ -86,12 +86,16 @@ struct TokenTreeIter { } impl TokenTreeIter { + /// Constructs a new `TokenTreeIter` from a given `proc_macro::Literal`. + /// + /// # Panics + /// This panics if the given `Literal` is not a string literal. fn new(input: Literal) -> Self { let mut buf: Vec = input.to_string().into(); match buf.as_slice() { [b'"', .., b'"'] => (), - _ => panic!("expected string literals"), + _ => panic!("expected string literal, got `{}`", input), }; buf.pop(); let mut iter = buf.into_iter().exclude_comments(); @@ -102,6 +106,11 @@ impl TokenTreeIter { } } + /// Parses a single hex character (a-f/A-F/0-9) as a `u8` from the `TokenTreeIter`'s + /// internal buffer, ignoring whitespace. + /// + /// # Panics + /// This panics if a non-hex, non-whitespace character is encountered. fn next_hex_val(&mut self) -> Option { loop { let v = self.buf.next()?; @@ -110,7 +119,8 @@ impl TokenTreeIter { b'A'..=b'F' => v - 55, b'a'..=b'f' => v - 87, b' ' | b'\r' | b'\n' | b'\t' => continue, - _ => panic!("encountered invalid character"), + 0..=127 => panic!("encountered invalid character: `{}`", v as char), + _ => panic!("encountered invalid non-ASCII character"), }; return Some(n); } @@ -120,6 +130,13 @@ impl TokenTreeIter { impl Iterator for TokenTreeIter { type Item = TokenTree; + /// Produces hex values (as `u8` literals) parsed from the `TokenTreeIter`'s + /// internal buffer, alternating with commas to separate the elements of the + /// generated array of bytes. + /// + /// # Panics + /// This panics if the internal buffer contains an odd number of hex + /// characters. fn next(&mut self) -> Option { let v = if self.is_punct { TokenTree::Punct(Punct::new(',', Spacing::Alone)) @@ -145,7 +162,7 @@ pub fn hex(input: TokenStream) -> TokenStream { for tt in ignore_groups(input) { let iter = match tt { TokenTree::Literal(literal) => TokenTreeIter::new(literal), - _ => panic!("expected string literals"), + unexpected => panic!("expected string literal, got `{}`", unexpected), }; out_ts.extend(iter); } diff --git a/hex-literal/tests/basic.rs b/hex-literal/tests/basic.rs new file mode 100644 index 00000000..b4af38ec --- /dev/null +++ b/hex-literal/tests/basic.rs @@ -0,0 +1,83 @@ +use hex_literal::hex; + +#[test] +fn single_literal() { + assert_eq!(hex!("ff e4"), [0xff, 0xe4]); +} + +#[test] +fn empty() { + let nothing: [u8; 0] = hex!(); + let empty_literals: [u8; 0] = hex!("" "" ""); + let expected: [u8; 0] = []; + assert_eq!(nothing, expected); + assert_eq!(empty_literals, expected); +} + +#[test] +fn upper_case() { + assert_eq!(hex!("AE DF 04 B2"), [0xae, 0xdf, 0x04, 0xb2]); + assert_eq!(hex!("FF BA 8C 00 01"), [0xff, 0xba, 0x8c, 0x00, 0x01]); +} + +#[test] +fn mixed_case() { + assert_eq!(hex!("bF dd E4 Cd"), [0xbf, 0xdd, 0xe4, 0xcd]); +} + +#[test] +fn multiple_literals() { + assert_eq!( + hex!( + "01 dd f7 7f" + "ee f0 d8" + ), + [0x01, 0xdd, 0xf7, 0x7f, 0xee, 0xf0, 0xd8] + ); + assert_eq!( + hex!( + "ff" + "e8 d0" + "" + "01 1f" + "ab" + ), + [0xff, 0xe8, 0xd0, 0x01, 0x1f, 0xab] + ); +} + +#[test] +fn no_spacing() { + assert_eq!(hex!("abf0d8bb0f14"), [0xab, 0xf0, 0xd8, 0xbb, 0x0f, 0x14]); + assert_eq!( + hex!("09FFd890cbcCd1d08F"), + [0x09, 0xff, 0xd8, 0x90, 0xcb, 0xcc, 0xd1, 0xd0, 0x8f] + ); +} + +#[test] +fn allows_various_spacing() { + // newlines + assert_eq!( + hex!( + "f + f + d + 0 + e + + 8 + " + ), + [0xff, 0xd0, 0xe8] + ); + // tabs + assert_eq!(hex!("9f d 1 f07 3 01 "), [0x9f, 0xd1, 0xf0, 0x73, 0x01]); + // spaces + assert_eq!(hex!(" e e d0 9 1 f f "), [0xee, 0xd0, 0x91, 0xff]); +} + +#[test] +fn can_use_const() { + const _: [u8; 4] = hex!("ff d3 01 7f"); +} diff --git a/hex-literal/tests/comments.rs b/hex-literal/tests/comments.rs new file mode 100644 index 00000000..7ebb7eb7 --- /dev/null +++ b/hex-literal/tests/comments.rs @@ -0,0 +1,41 @@ +use hex_literal::hex; + +#[test] +fn single_line_comments() { + assert_eq!(hex!("dd 03 // comment"), [0xdd, 0x03]); + assert_eq!( + hex!( + "00 04 f0 // a comment here + 54 fe // another comment" + ), + [0x00, 0x04, 0xf0, 0x54, 0xfe] + ); + assert_eq!( + hex!( + "// initial comment + 01 02" + ), + [0x01, 0x02] + ); +} + +#[test] +fn block_comments() { + assert_eq!( + hex!("00 01 02 /* intervening comment */ 03 04"), + [0x00, 0x01, 0x02, 0x03, 0x04] + ); + assert_eq!(hex!("/* initial comment */ ff df dd"), [0xff, 0xdf, 0xdd]); + assert_eq!( + hex!( + "8f ff 7d /* + comment + on + several + lines + */ + d0 a3" + ), + [0x8f, 0xff, 0x7d, 0xd0, 0xa3] + ); +} diff --git a/inout/CHANGELOG.md b/inout/CHANGELOG.md new file mode 100644 index 00000000..e40ad40d --- /dev/null +++ b/inout/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.1.2 (2022-02-10) +### Changed +- Use borrow instead of consuming in `InOutBufReserved::get_*_len()` methods ([#734]) + +[#734]: https://github.com/RustCrypto/utils/pull/734 + +## 0.1.1 (2022-02-10) +### Fixed +- Fix doc build on docs.rs by optionally enabling the `doc_cfg` feature ([#733]) + +[#733]: https://github.com/RustCrypto/utils/pull/733 + +## 0.1.0 (2022-02-10) +- Initial release ([#675]) + +[#675]: https://github.com/RustCrypto/utils/pull/675 diff --git a/inout/Cargo.lock b/inout/Cargo.lock new file mode 100644 index 00000000..2455765e --- /dev/null +++ b/inout/Cargo.lock @@ -0,0 +1,40 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "block-padding" +version = "0.3.1" +dependencies = [ + "generic-array", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "inout" +version = "0.1.2" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" diff --git a/inout/Cargo.toml b/inout/Cargo.toml new file mode 100644 index 00000000..af4415e3 --- /dev/null +++ b/inout/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "inout" +version = "0.1.2" # Also update html_root_url in lib.rs when bumping this +description = "Custom reference types for code generic over in-place and buffer-to-buffer modes of operation." +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +edition = "2021" +rust-version = "1.56" +documentation = "https://docs.rs/inout" +repository = "https://github.com/RustCrypto/utils" +keywords = ["custom-reference"] + +# Hack to allow this crate to coexist with pre-2021 edition crates +[workspace] +members = ["."] + +[dependencies] +generic-array = "0.14" +block-padding = { version = "0.3", path = "../block-padding", optional = true } + +[features] +std = ["block-padding/std"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/base64ct/LICENSE-APACHE b/inout/LICENSE-APACHE similarity index 100% rename from base64ct/LICENSE-APACHE rename to inout/LICENSE-APACHE diff --git a/const-oid/LICENSE-MIT b/inout/LICENSE-MIT similarity index 92% rename from const-oid/LICENSE-MIT rename to inout/LICENSE-MIT index 2726e14a..26af9406 100644 --- a/const-oid/LICENSE-MIT +++ b/inout/LICENSE-MIT @@ -1,4 +1,5 @@ -Copyright (c) 2020 The RustCrypto Project Developers +Copyright (c) 2022 The RustCrypto Project Developers +Copyright (c) 2022 Artyom Pavlov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/inout/src/errors.rs b/inout/src/errors.rs new file mode 100644 index 00000000..be61a780 --- /dev/null +++ b/inout/src/errors.rs @@ -0,0 +1,62 @@ +use core::fmt; + +/// The error returned when slice can not be converted into array. +#[derive(Copy, Clone, Debug)] +pub struct IntoArrayError; + +impl fmt::Display for IntoArrayError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Failed to convert into array.") + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for IntoArrayError {} + +/// The error returned when input and output slices have different length +/// and thus can not be converted to `InOutBuf`. +#[derive(Copy, Clone, Debug)] +pub struct NotEqualError; + +impl fmt::Display for NotEqualError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Length of input slices is not equal to each other") + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for NotEqualError {} + +/// Padding error. Usually emitted when size of output buffer is insufficient. +#[cfg(feature = "block-padding")] +#[cfg_attr(docsrs, doc(cfg(feature = "block-padding")))] +#[derive(Clone, Copy, Debug)] +pub struct PadError; + +#[cfg(feature = "block-padding")] +impl fmt::Display for PadError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Padding error") + } +} + +#[cfg(feature = "block-padding")] +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for PadError {} + +/// Output buffer is smaller than input buffer. +#[derive(Clone, Copy, Debug)] +pub struct OutIsTooSmallError; + +impl fmt::Display for OutIsTooSmallError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + f.write_str("Output buffer is smaller than input") + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for OutIsTooSmallError {} diff --git a/inout/src/inout.rs b/inout/src/inout.rs new file mode 100644 index 00000000..8e798865 --- /dev/null +++ b/inout/src/inout.rs @@ -0,0 +1,174 @@ +use crate::InOutBuf; +use core::{marker::PhantomData, ptr}; +use generic_array::{ArrayLength, GenericArray}; + +/// Custom pointer type which contains one immutable (input) and one mutable +/// (output) pointer, which are either equal or non-overlapping. +pub struct InOut<'inp, 'out, T> { + pub(crate) in_ptr: *const T, + pub(crate) out_ptr: *mut T, + pub(crate) _pd: PhantomData<(&'inp T, &'out mut T)>, +} + +impl<'inp, 'out, T> InOut<'inp, 'out, T> { + /// Reborrow `self`. + #[inline(always)] + pub fn reborrow<'a>(&'a mut self) -> InOut<'a, 'a, T> { + Self { + in_ptr: self.in_ptr, + out_ptr: self.out_ptr, + _pd: PhantomData, + } + } + + /// Get immutable reference to the input value. + #[inline(always)] + pub fn get_in<'a>(&'a self) -> &'a T { + unsafe { &*self.in_ptr } + } + + /// Get mutable reference to the output value. + #[inline(always)] + pub fn get_out<'a>(&'a mut self) -> &'a mut T { + unsafe { &mut *self.out_ptr } + } + + /// Convert `self` to a pair of raw input and output pointers. + #[inline(always)] + pub fn into_raw(self) -> (*const T, *mut T) { + (self.in_ptr, self.out_ptr) + } + + /// Create `InOut` from raw input and output pointers. + /// + /// # Safety + /// Behavior is undefined if any of the following conditions are violated: + /// - `in_ptr` must point to a properly initialized value of type `T` and + /// must be valid for reads. + /// - `out_ptr` must point to a properly initialized value of type `T` and + /// must be valid for both reads and writes. + /// - `in_ptr` and `out_ptr` must be either equal or non-overlapping. + /// - If `in_ptr` and `out_ptr` are equal, then the memory referenced by + /// them must not be accessed through any other pointer (not derived from + /// the return value) for the duration of lifetime 'a. Both read and write + /// accesses are forbidden. + /// - If `in_ptr` and `out_ptr` are not equal, then the memory referenced by + /// `out_ptr` must not be accessed through any other pointer (not derived from + /// the return value) for the duration of lifetime `'a`. Both read and write + /// accesses are forbidden. The memory referenced by `in_ptr` must not be + /// mutated for the duration of lifetime `'a`, except inside an `UnsafeCell`. + #[inline(always)] + pub unsafe fn from_raw(in_ptr: *const T, out_ptr: *mut T) -> InOut<'inp, 'out, T> { + Self { + in_ptr, + out_ptr, + _pd: PhantomData, + } + } +} + +impl<'inp, 'out, T: Clone> InOut<'inp, 'out, T> { + /// Clone input value and return it. + #[inline(always)] + pub fn clone_in(&self) -> T { + unsafe { (&*self.in_ptr).clone() } + } +} + +impl<'a, T> From<&'a mut T> for InOut<'a, 'a, T> { + #[inline(always)] + fn from(val: &'a mut T) -> Self { + let out_ptr = val as *mut T; + Self { + in_ptr: out_ptr as *const T, + out_ptr, + _pd: PhantomData, + } + } +} + +impl<'inp, 'out, T> From<(&'inp T, &'out mut T)> for InOut<'inp, 'out, T> { + #[inline(always)] + fn from((in_val, out_val): (&'inp T, &'out mut T)) -> Self { + Self { + in_ptr: in_val as *const T, + out_ptr: out_val as *mut T, + _pd: Default::default(), + } + } +} + +impl<'inp, 'out, T, N: ArrayLength> InOut<'inp, 'out, GenericArray> { + /// Returns `InOut` for the given position. + /// + /// # Panics + /// If `pos` greater or equal to array length. + #[inline(always)] + pub fn get<'a>(&'a mut self, pos: usize) -> InOut<'a, 'a, T> { + assert!(pos < N::USIZE); + unsafe { + InOut { + in_ptr: (self.in_ptr as *const T).add(pos), + out_ptr: (self.out_ptr as *mut T).add(pos), + _pd: PhantomData, + } + } + } + + /// Convert `InOut` array to `InOutBuf`. + #[inline(always)] + pub fn into_buf(self) -> InOutBuf<'inp, 'out, T> { + InOutBuf { + in_ptr: self.in_ptr as *const T, + out_ptr: self.out_ptr as *mut T, + len: N::USIZE, + _pd: PhantomData, + } + } +} + +impl<'inp, 'out, N: ArrayLength> InOut<'inp, 'out, GenericArray> { + /// XOR `data` with values behind the input slice and write + /// result to the output slice. + /// + /// # Panics + /// If `data` length is not equal to the buffer length. + #[inline(always)] + #[allow(clippy::needless_range_loop)] + pub fn xor_in2out(&mut self, data: &GenericArray) { + unsafe { + let input = ptr::read(self.in_ptr); + let mut temp = GenericArray::::default(); + for i in 0..N::USIZE { + temp[i] = input[i] ^ data[i]; + } + ptr::write(self.out_ptr, temp); + } + } +} + +impl<'inp, 'out, N, M> InOut<'inp, 'out, GenericArray, M>> +where + N: ArrayLength, + M: ArrayLength>, +{ + /// XOR `data` with values behind the input slice and write + /// result to the output slice. + /// + /// # Panics + /// If `data` length is not equal to the buffer length. + #[inline(always)] + #[allow(clippy::needless_range_loop)] + pub fn xor_in2out(&mut self, data: &GenericArray, M>) { + unsafe { + let input = ptr::read(self.in_ptr); + let mut temp = GenericArray::, M>::default(); + for i in 0..M::USIZE { + for j in 0..N::USIZE { + temp[i][j] = input[i][j] ^ data[i][j]; + } + } + ptr::write(self.out_ptr, temp); + } + } +} diff --git a/inout/src/inout_buf.rs b/inout/src/inout_buf.rs new file mode 100644 index 00000000..0ab1b8dc --- /dev/null +++ b/inout/src/inout_buf.rs @@ -0,0 +1,302 @@ +use crate::{ + errors::{IntoArrayError, NotEqualError}, + InOut, +}; +use core::{marker::PhantomData, slice}; +use generic_array::{ArrayLength, GenericArray}; + +/// Custom slice type which references one immutable (input) slice and one +/// mutable (output) slice of equal length. Input and output slices are +/// either the same or do not overlap. +pub struct InOutBuf<'inp, 'out, T> { + pub(crate) in_ptr: *const T, + pub(crate) out_ptr: *mut T, + pub(crate) len: usize, + pub(crate) _pd: PhantomData<(&'inp T, &'out mut T)>, +} + +impl<'a, T> From<&'a mut [T]> for InOutBuf<'a, 'a, T> { + #[inline(always)] + fn from(buf: &'a mut [T]) -> Self { + Self { + in_ptr: buf.as_ptr(), + out_ptr: buf.as_mut_ptr(), + len: buf.len(), + _pd: PhantomData, + } + } +} + +impl<'a, T> InOutBuf<'a, 'a, T> { + /// Create `InOutBuf` from a single mutable reference. + #[inline(always)] + pub fn from_mut(val: &'a mut T) -> InOutBuf<'a, 'a, T> { + let out_ptr = val as *mut T; + Self { + in_ptr: out_ptr as *const T, + out_ptr, + len: 1, + _pd: PhantomData, + } + } +} + +impl<'inp, 'out, T> IntoIterator for InOutBuf<'inp, 'out, T> { + type Item = InOut<'inp, 'out, T>; + type IntoIter = InOutBufIter<'inp, 'out, T>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + InOutBufIter { buf: self, pos: 0 } + } +} + +impl<'inp, 'out, T> InOutBuf<'inp, 'out, T> { + /// Create `InOutBuf` from a pair of immutable and mutable references. + #[inline(always)] + pub fn from_ref_mut(in_val: &'inp T, out_val: &'out mut T) -> Self { + Self { + in_ptr: in_val as *const T, + out_ptr: out_val as *mut T, + len: 1, + _pd: PhantomData, + } + } + + /// Create `InOutBuf` from immutable and mutable slices. + /// + /// Returns an error if length of slices is not equal to each other. + #[inline(always)] + pub fn new(in_buf: &'inp [T], out_buf: &'out mut [T]) -> Result { + if in_buf.len() != out_buf.len() { + Err(NotEqualError) + } else { + Ok(Self { + in_ptr: in_buf.as_ptr(), + out_ptr: out_buf.as_mut_ptr(), + len: in_buf.len(), + _pd: Default::default(), + }) + } + } + + /// Get length of the inner buffers. + #[inline(always)] + pub fn len(&self) -> usize { + self.len + } + + /// Returns `true` if the buffer has a length of 0. + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns `InOut` for given position. + /// + /// # Panics + /// If `pos` greater or equal to buffer length. + #[inline(always)] + pub fn get<'a>(&'a mut self, pos: usize) -> InOut<'a, 'a, T> { + assert!(pos < self.len); + unsafe { + InOut { + in_ptr: self.in_ptr.add(pos), + out_ptr: self.out_ptr.add(pos), + _pd: PhantomData, + } + } + } + + /// Get input slice. + #[inline(always)] + pub fn get_in<'a>(&'a self) -> &'a [T] { + unsafe { slice::from_raw_parts(self.in_ptr, self.len) } + } + + /// Get output slice. + #[inline(always)] + pub fn get_out<'a>(&'a mut self) -> &'a mut [T] { + unsafe { slice::from_raw_parts_mut(self.out_ptr, self.len) } + } + + /// Consume self and return output slice with lifetime `'a`. + #[inline(always)] + pub fn into_out(self) -> &'out mut [T] { + unsafe { slice::from_raw_parts_mut(self.out_ptr, self.len) } + } + + /// Get raw input and output pointers. + #[inline(always)] + pub fn into_raw(self) -> (*const T, *mut T) { + (self.in_ptr, self.out_ptr) + } + + /// Reborrow `self`. + #[inline(always)] + pub fn reborrow<'a>(&'a mut self) -> InOutBuf<'a, 'a, T> { + Self { + in_ptr: self.in_ptr, + out_ptr: self.out_ptr, + len: self.len, + _pd: PhantomData, + } + } + + /// Create [`InOutBuf`] from raw input and output pointers. + /// + /// # Safety + /// Behavior is undefined if any of the following conditions are violated: + /// - `in_ptr` must point to a properly initialized value of type `T` and + /// must be valid for reads for `len * mem::size_of::()` many bytes. + /// - `out_ptr` must point to a properly initialized value of type `T` and + /// must be valid for both reads and writes for `len * mem::size_of::()` + /// many bytes. + /// - `in_ptr` and `out_ptr` must be either equal or non-overlapping. + /// - If `in_ptr` and `out_ptr` are equal, then the memory referenced by + /// them must not be accessed through any other pointer (not derived from + /// the return value) for the duration of lifetime 'a. Both read and write + /// accesses are forbidden. + /// - If `in_ptr` and `out_ptr` are not equal, then the memory referenced by + /// `out_ptr` must not be accessed through any other pointer (not derived from + /// the return value) for the duration of lifetime 'a. Both read and write + /// accesses are forbidden. The memory referenced by `in_ptr` must not be + /// mutated for the duration of lifetime `'a`, except inside an `UnsafeCell`. + /// - The total size `len * mem::size_of::()` must be no larger than `isize::MAX`. + #[inline(always)] + pub unsafe fn from_raw( + in_ptr: *const T, + out_ptr: *mut T, + len: usize, + ) -> InOutBuf<'inp, 'out, T> { + Self { + in_ptr, + out_ptr, + len, + _pd: PhantomData, + } + } + + /// Divides one buffer into two at `mid` index. + /// + /// The first will contain all indices from `[0, mid)` (excluding + /// the index `mid` itself) and the second will contain all + /// indices from `[mid, len)` (excluding the index `len` itself). + /// + /// # Panics + /// + /// Panics if `mid > len`. + #[inline(always)] + pub fn split_at(self, mid: usize) -> (InOutBuf<'inp, 'out, T>, InOutBuf<'inp, 'out, T>) { + assert!(mid <= self.len); + let (tail_in_ptr, tail_out_ptr) = unsafe { (self.in_ptr.add(mid), self.out_ptr.add(mid)) }; + ( + InOutBuf { + in_ptr: self.in_ptr, + out_ptr: self.out_ptr, + len: mid, + _pd: PhantomData, + }, + InOutBuf { + in_ptr: tail_in_ptr, + out_ptr: tail_out_ptr, + len: self.len() - mid, + _pd: PhantomData, + }, + ) + } + + /// Partition buffer into 2 parts: buffer of arrays and tail. + #[inline(always)] + pub fn into_chunks>( + self, + ) -> ( + InOutBuf<'inp, 'out, GenericArray>, + InOutBuf<'inp, 'out, T>, + ) { + let chunks = self.len() / N::USIZE; + let tail_pos = N::USIZE * chunks; + let tail_len = self.len() - tail_pos; + unsafe { + let chunks = InOutBuf { + in_ptr: self.in_ptr as *const GenericArray, + out_ptr: self.out_ptr as *mut GenericArray, + len: chunks, + _pd: PhantomData, + }; + let tail = InOutBuf { + in_ptr: self.in_ptr.add(tail_pos), + out_ptr: self.out_ptr.add(tail_pos), + len: tail_len, + _pd: PhantomData, + }; + (chunks, tail) + } + } +} + +impl<'inp, 'out> InOutBuf<'inp, 'out, u8> { + /// XORs `data` with values behind the input slice and write + /// result to the output slice. + /// + /// # Panics + /// If `data` length is not equal to the buffer length. + #[inline(always)] + #[allow(clippy::needless_range_loop)] + pub fn xor_in2out(&mut self, data: &[u8]) { + assert_eq!(self.len(), data.len()); + unsafe { + for i in 0..data.len() { + let in_ptr = self.in_ptr.add(i); + let out_ptr = self.out_ptr.add(i); + *out_ptr = *in_ptr ^ data[i]; + } + } + } +} + +impl<'inp, 'out, T, N> TryInto>> for InOutBuf<'inp, 'out, T> +where + N: ArrayLength, +{ + type Error = IntoArrayError; + + #[inline(always)] + fn try_into(self) -> Result>, Self::Error> { + if self.len() == N::USIZE { + Ok(InOut { + in_ptr: self.in_ptr as *const _, + out_ptr: self.out_ptr as *mut _, + _pd: PhantomData, + }) + } else { + Err(IntoArrayError) + } + } +} + +/// Iterator over [`InOutBuf`]. +pub struct InOutBufIter<'inp, 'out, T> { + buf: InOutBuf<'inp, 'out, T>, + pos: usize, +} + +impl<'inp, 'out, T> Iterator for InOutBufIter<'inp, 'out, T> { + type Item = InOut<'inp, 'out, T>; + + #[inline(always)] + fn next(&mut self) -> Option { + if self.buf.len() == self.pos { + return None; + } + let res = unsafe { + InOut { + in_ptr: self.buf.in_ptr.add(self.pos), + out_ptr: self.buf.out_ptr.add(self.pos), + _pd: PhantomData, + } + }; + self.pos += 1; + Some(res) + } +} diff --git a/inout/src/lib.rs b/inout/src/lib.rs new file mode 100644 index 00000000..2a92be1e --- /dev/null +++ b/inout/src/lib.rs @@ -0,0 +1,26 @@ +//! Collection of custom reference types for code generic over in-place and +//! buffer-to-buffer modes of operation. + +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", + html_root_url = "https://docs.rs/inout/0.1.2" +)] +#![allow(clippy::needless_lifetimes)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] + +#[cfg(feature = "std")] +extern crate std; + +#[cfg(feature = "block-padding")] +#[cfg_attr(docsrs, doc(cfg(feature = "block-padding")))] +pub use block_padding; + +mod errors; +mod inout; +mod inout_buf; +mod reserved; + +pub use crate::{errors::*, inout::*, inout_buf::*, reserved::*}; diff --git a/inout/src/reserved.rs b/inout/src/reserved.rs new file mode 100644 index 00000000..d437106b --- /dev/null +++ b/inout/src/reserved.rs @@ -0,0 +1,226 @@ +use crate::errors::OutIsTooSmallError; +use core::{marker::PhantomData, slice}; + +#[cfg(feature = "block-padding")] +use crate::errors::PadError; +#[cfg(feature = "block-padding")] +use crate::{InOut, InOutBuf}; +#[cfg(feature = "block-padding")] +use block_padding::{PadType, Padding}; +#[cfg(feature = "block-padding")] +use generic_array::{ArrayLength, GenericArray}; + +/// Custom slice type which references one immutable (input) slice and one +/// mutable (output) slice. Input and output slices are either the same or +/// do not overlap. Length of the output slice is always equal or bigger than +/// length of the input slice. +pub struct InOutBufReserved<'inp, 'out, T> { + in_ptr: *const T, + out_ptr: *mut T, + in_len: usize, + out_len: usize, + _pd: PhantomData<(&'inp T, &'out mut T)>, +} + +impl<'a, T> InOutBufReserved<'a, 'a, T> { + /// Crate [`InOutBufReserved`] from a single mutable slice. + pub fn from_mut_slice(buf: &'a mut [T], msg_len: usize) -> Result { + if msg_len > buf.len() { + return Err(OutIsTooSmallError); + } + let out_ptr = buf.as_mut_ptr(); + let out_len = buf.len(); + Ok(Self { + in_ptr: out_ptr as *const T, + out_ptr, + in_len: msg_len, + out_len, + _pd: PhantomData, + }) + } + + /// Create [`InOutBufReserved`] from raw input and output pointers. + /// + /// # Safety + /// Behavior is undefined if any of the following conditions are violated: + /// - `in_ptr` must point to a properly initialized value of type `T` and + /// must be valid for reads for `in_len * mem::size_of::()` many bytes. + /// - `out_ptr` must point to a properly initialized value of type `T` and + /// must be valid for both reads and writes for `out_len * mem::size_of::()` + /// many bytes. + /// - `in_ptr` and `out_ptr` must be either equal or non-overlapping. + /// - If `in_ptr` and `out_ptr` are equal, then the memory referenced by + /// them must not be accessed through any other pointer (not derived from + /// the return value) for the duration of lifetime 'a. Both read and write + /// accesses are forbidden. + /// - If `in_ptr` and `out_ptr` are not equal, then the memory referenced by + /// `out_ptr` must not be accessed through any other pointer (not derived from + /// the return value) for the duration of lifetime 'a. Both read and write + /// accesses are forbidden. The memory referenced by `in_ptr` must not be + /// mutated for the duration of lifetime `'a`, except inside an `UnsafeCell`. + /// - The total size `in_len * mem::size_of::()` and + /// `out_len * mem::size_of::()` must be no larger than `isize::MAX`. + #[inline(always)] + pub unsafe fn from_raw( + in_ptr: *const T, + in_len: usize, + out_ptr: *mut T, + out_len: usize, + ) -> Self { + Self { + in_ptr, + out_ptr, + in_len, + out_len, + _pd: PhantomData, + } + } + + /// Get raw input and output pointers. + #[inline(always)] + pub fn into_raw(self) -> (*const T, *mut T) { + (self.in_ptr, self.out_ptr) + } + + /// Get input buffer length. + #[inline(always)] + pub fn get_in_len(&self) -> usize { + self.in_len + } + + /// Get output buffer length. + #[inline(always)] + pub fn get_out_len(&self) -> usize { + self.in_len + } +} + +impl<'inp, 'out, T> InOutBufReserved<'inp, 'out, T> { + /// Crate [`InOutBufReserved`] from two separate slices. + pub fn from_slices( + in_buf: &'inp [T], + out_buf: &'out mut [T], + ) -> Result { + if in_buf.len() > out_buf.len() { + return Err(OutIsTooSmallError); + } + Ok(Self { + in_ptr: in_buf.as_ptr(), + out_ptr: out_buf.as_mut_ptr(), + in_len: in_buf.len(), + out_len: out_buf.len(), + _pd: PhantomData, + }) + } + + /// Get input slice. + #[inline(always)] + pub fn get_in<'a>(&'a self) -> &'a [T] { + unsafe { slice::from_raw_parts(self.in_ptr, self.in_len) } + } + + /// Get output slice. + #[inline(always)] + pub fn get_out<'a>(&'a mut self) -> &'a mut [T] { + unsafe { slice::from_raw_parts_mut(self.out_ptr, self.out_len) } + } +} + +impl<'inp, 'out> InOutBufReserved<'inp, 'out, u8> { + /// Transform buffer into [`PaddedInOutBuf`] using padding algorithm `P`. + #[cfg(feature = "block-padding")] + #[cfg_attr(docsrs, doc(cfg(feature = "block-padding")))] + #[inline(always)] + pub fn into_padded_blocks(self) -> Result, PadError> + where + P: Padding, + BS: ArrayLength, + { + let bs = BS::USIZE; + let blocks_len = self.in_len / bs; + let tail_len = self.in_len - bs * blocks_len; + let blocks = unsafe { + InOutBuf::from_raw( + self.in_ptr as *const GenericArray, + self.out_ptr as *mut GenericArray, + blocks_len, + ) + }; + let mut tail_in = GenericArray::::default(); + let tail_out = match P::TYPE { + PadType::NoPadding | PadType::Ambiguous if tail_len == 0 => None, + PadType::NoPadding => return Err(PadError), + PadType::Reversible | PadType::Ambiguous => { + let blen = bs * blocks_len; + let res_len = blen + bs; + if res_len > self.out_len { + return Err(PadError); + } + // SAFETY: `in_ptr + blen..in_ptr + blen + tail_len` + // is valid region for reads and `tail_len` is smaller than `BS`. + // we have verified that `blen + bs <= out_len`, in other words, + // `out_ptr + blen..out_ptr + blen + bs` is valid region + // for writes. + let out_block = unsafe { + core::ptr::copy_nonoverlapping( + self.in_ptr.add(blen), + tail_in.as_mut_ptr(), + tail_len, + ); + &mut *(self.out_ptr.add(blen) as *mut GenericArray) + }; + P::pad(&mut tail_in, tail_len); + Some(out_block) + } + }; + Ok(PaddedInOutBuf { + blocks, + tail_in, + tail_out, + }) + } +} + +/// Variant of [`InOutBuf`] with optional padded tail block. +#[cfg(feature = "block-padding")] +#[cfg_attr(docsrs, doc(cfg(feature = "block-padding")))] +pub struct PaddedInOutBuf<'inp, 'out, BS: ArrayLength> { + blocks: InOutBuf<'inp, 'out, GenericArray>, + tail_in: GenericArray, + tail_out: Option<&'out mut GenericArray>, +} + +#[cfg(feature = "block-padding")] +impl<'inp, 'out, BS: ArrayLength> PaddedInOutBuf<'inp, 'out, BS> { + /// Get full blocks. + #[inline(always)] + pub fn get_blocks<'a>(&'a mut self) -> InOutBuf<'a, 'a, GenericArray> { + self.blocks.reborrow() + } + + /// Get padded tail block. + /// + /// For paddings with `P::TYPE = PadType::Reversible` it always returns `Some`. + #[inline(always)] + pub fn get_tail_block<'a>(&'a mut self) -> Option>> { + match self.tail_out.as_deref_mut() { + Some(out_block) => Some((&self.tail_in, out_block).into()), + None => None, + } + } + + /// Convert buffer into output slice. + #[inline(always)] + pub fn into_out(self) -> &'out [u8] { + let total_blocks = if self.tail_out.is_some() { + self.blocks.len() + 1 + } else { + self.blocks.len() + }; + let res_len = BS::USIZE * total_blocks; + let (_, out_ptr) = self.blocks.into_raw(); + // SAFETY: `res_len` is always valid for the output buffer since + // it's checked during type construction + unsafe { slice::from_raw_parts(out_ptr as *const u8, res_len) } + } +} diff --git a/opaque-debug/src/lib.rs b/opaque-debug/src/lib.rs index 72b0d073..86163ad6 100644 --- a/opaque-debug/src/lib.rs +++ b/opaque-debug/src/lib.rs @@ -1,8 +1,8 @@ //! Macro for opaque `Debug` trait implementation. #![no_std] #![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", html_root_url = "https://docs.rs/opaque-debug/0.3.0" )] diff --git a/pem-rfc7468/CHANGELOG.md b/pem-rfc7468/CHANGELOG.md deleted file mode 100644 index 1e3499ad..00000000 --- a/pem-rfc7468/CHANGELOG.md +++ /dev/null @@ -1,24 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.2.0 (2021-07-26) -### Added -- Support for customizing PEM line endings ([#551]) - -[#551]: https://github.com/RustCrypto/utils/pull/551 - -## 0.1.1 (2021-07-24) -### Changed -- Increase LF precedence in EOL stripping functions ([#532]) - -### Fixed -- Bug in the size calculation for `decode_vec` ([#530]) - -[#530]: https://github.com/RustCrypto/utils/pull/530 -[#532]: https://github.com/RustCrypto/utils/pull/532 - -## 0.1.0 (2021-07-23) -- Initial release diff --git a/pem-rfc7468/Cargo.toml b/pem-rfc7468/Cargo.toml deleted file mode 100644 index 60858e32..00000000 --- a/pem-rfc7468/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "pem-rfc7468" -version = "0.2.0" # Also update html_root_url in lib.rs when bumping this -description = """ -PEM Encoding (RFC 7468) for PKIX, PKCS, and CMS Structures, implementing a -strict subset of the original Privacy-Enhanced Mail encoding intended -specifically for use with cryptographic keys, certificates, and other messages. -Provides a no_std-friendly, constant-time implementation suitable for use with -cryptographic private keys. -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -repository = "https://github.com/RustCrypto/utils/tree/master/pem-rfc7468" -categories = ["cryptography", "data-structures", "encoding", "no-std", "parser-implementations"] -keywords = ["crypto", "key", "pem", "pkcs", "rsa"] -readme = "README.md" - -[dependencies] -base64ct = { version = "1", path = "../base64ct" } - -[features] -alloc = [] -std = ["alloc"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/pem-rfc7468/LICENSE-APACHE b/pem-rfc7468/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/pem-rfc7468/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/pem-rfc7468/LICENSE-MIT b/pem-rfc7468/LICENSE-MIT deleted file mode 100644 index c869ada5..00000000 --- a/pem-rfc7468/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2021 The RustCrypto Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/pem-rfc7468/README.md b/pem-rfc7468/README.md deleted file mode 100644 index 161ab89e..00000000 --- a/pem-rfc7468/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# RustCrypto: PEM Encoding ([RFC 7468]) - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Pure Rust implementation of PEM Encoding ([RFC 7468]) for PKIX, PKCS, and -CMS Structures, a strict subset of the original Privacy-Enhanced Mail encoding -intended specifically for use with cryptographic keys, certificates, and other -messages. - -Provides a `no_std`-friendly, constant-time implementation suitable for use with -cryptographic private keys. - -[Documentation][docs-link] - -## About - -Many cryptography-related document formats, such as certificates (PKIX), -private and public keys/keypairs (PKCS), and other cryptographic messages (CMS) -provide an ASCII encoding which can be traced back to Privacy-Enhanced Mail -(PEM) as defined [RFC 1421], which look like the following: - -```text ------BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIBftnHPp22SewYmmEoMcX8VwI4IHwaqd+9LFPj/15eqF ------END PRIVATE KEY----- -``` - -However, all of these formats actually implement a text-based encoding that is -similar to, but *not* identical with, the legacy PEM encoding as described in -[RFC 1421]. - -For this reason, [RFC 7468] was created to describe a stricter form of -"PEM encoding" for use in these applications which codifies the previously -de facto rules that most implementations operate by, and makes recommendations -to promote interoperability. - -This crate attempts to implement a strict interpretation of the [RFC 7468] -rules, implementing all of the MUSTs and SHOULDs while avoiding the MAYs, -and targeting the "ABNF (Strict)" subset of the grammar as described in -Section 3 Figure 3. - -## Implementation notes - -- Core PEM implementation is `no_std`-friendly and requires no heap allocations. -- Avoids use of copies and temporary buffers. -- Uses the [`base64ct`] crate to decode/encode Base64 in constant-time. -- PEM parser avoids branching on potentially secret data as much as possible. - In the happy path, only 1-byte of secret data is potentially - branched upon. - -Note: a forthcoming paper [Util::Lookup: Exploiting key decoding in cryptographic libraries][Util::Lookup] -demonstrates how the leakage from non-constant-time PEM parsers can be used -to practically extract RSA private keys from SGX enclaves. - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/pem-rfc7468.svg -[crate-link]: https://crates.io/crates/pem-rfc7468 -[docs-image]: https://docs.rs/pem-rfc7468/badge.svg -[docs-link]: https://docs.rs/pem-rfc7468/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/pem-rfc7468/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions - -[//]: # (links) - -[RFC 1421]: https://datatracker.ietf.org/doc/html/rfc1421 -[RFC 7468]: https://datatracker.ietf.org/doc/html/rfc7468 -[`base64ct`]: https://github.com/RustCrypto/utils/tree/master/base64ct -[Util::Lookup]: https://twitter.com/JanWichelmann/status/1418532480081145857 diff --git a/pem-rfc7468/src/decoder.rs b/pem-rfc7468/src/decoder.rs deleted file mode 100644 index 44101e07..00000000 --- a/pem-rfc7468/src/decoder.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! Decoder for PEM encapsulated data. -//! -//! From RFC 7468 Section 2: -//! -//! > Textual encoding begins with a line comprising "-----BEGIN ", a -//! > label, and "-----", and ends with a line comprising "-----END ", a -//! > label, and "-----". Between these lines, or "encapsulation -//! > boundaries", are base64-encoded data according to Section 4 of -//! > [RFC 4648]. -//! -//! [RFC 4648]: https://datatracker.ietf.org/doc/html/rfc4648 - -#[cfg(feature = "alloc")] -use alloc::vec::Vec; - -use crate::{ - grammar, Error, Result, BASE64_WRAP_WIDTH, POST_ENCAPSULATION_BOUNDARY, - PRE_ENCAPSULATION_BOUNDARY, -}; -use base64ct::{Base64, Encoding}; -use core::{convert::TryFrom, str}; - -/// Decode a PEM document according to RFC 7468's "Strict" grammar. -/// -/// On success, writes the decoded document into the provided buffer, returning -/// the decoded label and the portion of the provided buffer containing the -/// decoded message. -pub fn decode<'i, 'o>(pem: &'i [u8], buf: &'o mut [u8]) -> Result<(&'i str, &'o [u8])> { - let encapsulation = Encapsulation::try_from(pem)?; - let label = encapsulation.label(); - let mut out_len = 0; - - for line in encapsulation.encapsulated_text() { - out_len += Base64::decode(line?, &mut buf[out_len..])?.len(); - } - - Ok((label, &buf[..out_len])) -} - -/// Decode a PEM document according to RFC 7468's "Strict" grammar, returning -/// the result as a [`Vec`] upon success. -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub fn decode_vec(pem: &[u8]) -> Result<(&str, Vec)> { - let encapsulation = Encapsulation::try_from(pem)?; - let label = encapsulation.label(); - let mut max_len = 0; - - for line in encapsulation.encapsulated_text() { - max_len += line?.len() * 3 / 4; - } - - let mut result = vec![0u8; max_len]; - let mut actual_len = 0; - for line in encapsulation.encapsulated_text() { - actual_len += Base64::decode(line?, &mut result[actual_len..])?.len(); - } - - // Actual encoded length can be slightly shorter than estimated - // TODO(tarcieri): more reliable length estimation - result.truncate(actual_len); - Ok((label, result)) -} - -/// PEM encapsulation parser. -/// -/// This parser performs an initial pass over the data, locating the -/// pre-encapsulation (`---BEGIN [...]---`) and post-encapsulation -/// (`---END [...]`) boundaries while attempting to avoid branching -/// on the potentially secret Base64-encoded data encapsulated between -/// the two boundaries. -/// -/// It only supports a single encapsulated message at present. Future work -/// could potentially include extending it provide an iterator over a series -/// of encapsulated messages. -#[derive(Copy, Clone, Debug)] -struct Encapsulation<'a> { - /// Type label extracted from the pre/post-encapsulation boundaries. - /// - /// From RFC 7468 Section 2: - /// - /// > The type of data encoded is labeled depending on the type label in - /// > the "-----BEGIN " line (pre-encapsulation boundary). For example, - /// > the line may be "-----BEGIN CERTIFICATE-----" to indicate that the - /// > content is a PKIX certificate (see further below). Generators MUST - /// > put the same label on the "-----END " line (post-encapsulation - /// > boundary) as the corresponding "-----BEGIN " line. Labels are - /// > formally case-sensitive, uppercase, and comprised of zero or more - /// > characters; they do not contain consecutive spaces or hyphen-minuses, - /// > nor do they contain spaces or hyphen-minuses at either end. Parsers - /// > MAY disregard the label in the post-encapsulation boundary instead of - /// > signaling an error if there is a label mismatch: some extant - /// > implementations require the labels to match; others do not. - label: &'a str, - - /// Encapsulated text portion contained between the boundaries. - /// - /// This data should be encoded as Base64, however this type performs no - /// validation of it so it can be handled in constant-time. - encapsulated_text: &'a [u8], -} - -impl<'a> Encapsulation<'a> { - /// Parse the type label and encapsulated text from between the - /// pre/post-encapsulation boundaries. - /// - /// Note that the current implementation does not permit data before the - /// pre-encapsulation bounadry. This may technically be in violation of - /// RFC 7468: - /// > Data before the encapsulation boundaries are permitted, and - /// > parsers MUST NOT malfunction when processing such data. - // TODO(tarcieri): determine what is allowed before the pre-encapsulation boundary - pub fn parse(data: &'a [u8]) -> Result { - // Parse pre-encapsulation boundary (including label) - let data = data - .strip_prefix(PRE_ENCAPSULATION_BOUNDARY) - .ok_or(Error::PreEncapsulationBoundary)?; - - let (label, body) = grammar::split_label(data).ok_or(Error::Label)?; - - let mut body = match grammar::strip_trailing_eol(body).unwrap_or(body) { - [head @ .., b'-', b'-', b'-', b'-', b'-'] => head, - _ => return Err(Error::PreEncapsulationBoundary), - }; - - // Ensure body ends with a properly labeled post-encapsulation boundary - for &slice in [POST_ENCAPSULATION_BOUNDARY, label.as_bytes()].iter().rev() { - // Ensure the input ends with the post encapsulation boundary as - // well as a matching label - if !body.ends_with(slice) { - return Err(Error::PostEncapsulationBoundary); - } - - body = body - .get(..(body.len() - slice.len())) - .ok_or(Error::PostEncapsulationBoundary)?; - } - - let encapsulated_text = - grammar::strip_trailing_eol(body).ok_or(Error::PostEncapsulationBoundary)?; - - Ok(Self { - label, - encapsulated_text, - }) - } - - /// Get the label parsed from the encapsulation boundaries. - pub fn label(self) -> &'a str { - self.label - } - - /// Get an iterator over the (allegedly) Base64-encoded lines of the - /// encapsulated text. - pub fn encapsulated_text(self) -> Lines<'a> { - Lines { - bytes: self.encapsulated_text, - } - } -} - -impl<'a> TryFrom<&'a [u8]> for Encapsulation<'a> { - type Error = Error; - - fn try_from(bytes: &'a [u8]) -> Result { - Self::parse(bytes) - } -} - -/// Iterator over the lines in the encapsulated text. -struct Lines<'a> { - /// Remaining data being iterated over. - bytes: &'a [u8], -} - -impl<'a> Iterator for Lines<'a> { - type Item = Result<&'a [u8]>; - - fn next(&mut self) -> Option { - if self.bytes.len() > BASE64_WRAP_WIDTH { - let (line, rest) = self.bytes.split_at(BASE64_WRAP_WIDTH); - let result = grammar::strip_leading_eol(rest) - .ok_or(Error::EncapsulatedText) - .map(|rest| { - self.bytes = rest; - line - }); - Some(result) - } else if !self.bytes.is_empty() { - let line = self.bytes; - self.bytes = &[]; - Some(Ok(line)) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::Encapsulation; - - #[test] - fn pkcs8_example() { - let pem = include_bytes!("../tests/examples/pkcs8.pem"); - let result = Encapsulation::parse(pem).unwrap(); - assert_eq!(result.label, "PRIVATE KEY"); - - let mut lines = result.encapsulated_text(); - assert_eq!( - lines.next().unwrap().unwrap(), - &[ - 77, 67, 52, 67, 65, 81, 65, 119, 66, 81, 89, 68, 75, 50, 86, 119, 66, 67, 73, 69, - 73, 66, 102, 116, 110, 72, 80, 112, 50, 50, 83, 101, 119, 89, 109, 109, 69, 111, - 77, 99, 88, 56, 86, 119, 73, 52, 73, 72, 119, 97, 113, 100, 43, 57, 76, 70, 80, - 106, 47, 49, 53, 101, 113, 70 - ] - ); - assert_eq!(lines.next(), None); - } -} diff --git a/pem-rfc7468/src/encoder.rs b/pem-rfc7468/src/encoder.rs deleted file mode 100644 index 6fe30503..00000000 --- a/pem-rfc7468/src/encoder.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! PEM encoder. - -use crate::{ - grammar::{self, CHAR_CR, CHAR_LF}, - Error, Result, BASE64_WRAP_WIDTH, ENCAPSULATION_BOUNDARY_DELIMITER, - POST_ENCAPSULATION_BOUNDARY, PRE_ENCAPSULATION_BOUNDARY, -}; -use base64ct::{Base64, Encoding}; - -#[cfg(feature = "alloc")] -use alloc::string::String; - -/// Encode a PEM document according to RFC 7468's "Strict" grammar. -pub fn encode<'a>( - label: &str, - line_ending: LineEnding, - input: &[u8], - buf: &'a mut [u8], -) -> Result<&'a [u8]> { - grammar::validate_label(label.as_bytes())?; - - let mut buf = Buffer::new(buf, line_ending); - buf.write(PRE_ENCAPSULATION_BOUNDARY)?; - buf.write(label.as_bytes())?; - buf.writeln(ENCAPSULATION_BOUNDARY_DELIMITER)?; - - for chunk in input.chunks((BASE64_WRAP_WIDTH * 3) / 4) { - buf.write_base64ln(chunk)?; - } - - buf.write(POST_ENCAPSULATION_BOUNDARY)?; - buf.write(label.as_bytes())?; - buf.writeln(ENCAPSULATION_BOUNDARY_DELIMITER)?; - buf.finish() -} - -/// Get the length of a PEM encoded document with the given bytes and label. -pub fn encoded_len(label: &str, line_ending: LineEnding, input: &[u8]) -> usize { - // TODO(tarcieri): use checked arithmetic - PRE_ENCAPSULATION_BOUNDARY.len() - + label.as_bytes().len() - + ENCAPSULATION_BOUNDARY_DELIMITER.len() - + line_ending.len() - + input - .chunks((BASE64_WRAP_WIDTH * 3) / 4) - .fold(0, |acc, chunk| { - acc + Base64::encoded_len(chunk) + line_ending.len() - }) - + POST_ENCAPSULATION_BOUNDARY.len() - + label.as_bytes().len() - + ENCAPSULATION_BOUNDARY_DELIMITER.len() - + line_ending.len() -} - -/// Encode a PEM document according to RFC 7468's "Strict" grammar, returning -/// the result as a [`String`]. -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub fn encode_string(label: &str, line_ending: LineEnding, input: &[u8]) -> Result { - let mut buf = vec![0u8; encoded_len(label, line_ending, input)]; - encode(label, line_ending, input, &mut buf)?; - String::from_utf8(buf).map_err(|_| Error::CharacterEncoding) -} - -/// Line endings. -/// -/// Use [`LineEnding::default`] to get an appropriate line ending for the -/// current operating system. -#[allow(clippy::upper_case_acronyms)] -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub enum LineEnding { - /// Carriage return: `\r` (Pre-OS X Macintosh) - CR, - - /// Line feed: `\n` (Unix OSes) - LF, - - /// Carriage return + line feed: `\r\n` (Windows) - CRLF, -} - -impl Default for LineEnding { - /// Use the line ending for the current OS - #[cfg(windows)] - fn default() -> LineEnding { - LineEnding::CRLF - } - #[cfg(not(windows))] - fn default() -> LineEnding { - LineEnding::LF - } -} - -#[allow(clippy::len_without_is_empty)] -impl LineEnding { - /// Get the byte serialization of this [`LineEnding`]. - pub fn as_bytes(self) -> &'static [u8] { - match self { - LineEnding::CR => &[CHAR_CR], - LineEnding::LF => &[CHAR_LF], - LineEnding::CRLF => &[CHAR_CR, CHAR_LF], - } - } - - /// Get the encoded length of this [`LineEnding`]. - pub fn len(self) -> usize { - self.as_bytes().len() - } -} - -/// Output buffer for writing encoded PEM output. -struct Buffer<'a> { - /// Backing byte slice where PEM output is being written. - bytes: &'a mut [u8], - - /// Total number of bytes written into the buffer so far. - position: usize, - - /// Line ending to use - line_ending: LineEnding, -} - -impl<'a> Buffer<'a> { - /// Initialize buffer. - pub fn new(bytes: &'a mut [u8], line_ending: LineEnding) -> Self { - Self { - bytes, - position: 0, - line_ending, - } - } - - /// Write a byte slice to the buffer. - pub fn write(&mut self, slice: &[u8]) -> Result<()> { - let reserved = self.reserve(slice.len())?; - reserved.copy_from_slice(slice); - Ok(()) - } - - /// Write a byte slice to the buffer with a newline at the end. - pub fn writeln(&mut self, slice: &[u8]) -> Result<()> { - self.write(slice)?; - self.write(self.line_ending.as_bytes()) - } - - /// Write Base64-encoded data to the buffer. - /// - /// Automatically adds a newline at the end. - pub fn write_base64ln(&mut self, bytes: &[u8]) -> Result<()> { - let reserved = self.reserve(Base64::encoded_len(bytes))?; - Base64::encode(bytes, reserved)?; - self.write(self.line_ending.as_bytes()) - } - - /// Finish writing to the buffer, returning the portion that has been - /// written to. - pub fn finish(self) -> Result<&'a [u8]> { - self.bytes.get(..self.position).ok_or(Error::Length) - } - - /// Reserve space in the encoding buffer, returning a mutable slice. - fn reserve(&mut self, nbytes: usize) -> Result<&mut [u8]> { - let new_position = self.position.checked_add(nbytes).ok_or(Error::Length)?; - - let reserved = self - .bytes - .get_mut(self.position..new_position) - .ok_or(Error::Length)?; - - self.position = new_position; - Ok(reserved) - } -} diff --git a/pem-rfc7468/src/error.rs b/pem-rfc7468/src/error.rs deleted file mode 100644 index bbf2fb6b..00000000 --- a/pem-rfc7468/src/error.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Error types - -use core::fmt; - -/// Result type. -pub type Result = core::result::Result; - -/// Error type. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub enum Error { - /// Base64-related errors. - Base64, - - /// Character encoding-related errors. - CharacterEncoding, - - /// Errors in the encapsulated text (which aren't specifically Base64-related). - EncapsulatedText, - - /// Invalid label. - Label, - - /// Invalid length. - Length, - - /// Errors in the pre-encapsulation boundary. - PreEncapsulationBoundary, - - /// Errors in the post-encapsulation boundary. - PostEncapsulationBoundary, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Error::Base64 => "PEM Base64 error", - Error::CharacterEncoding => "PEM character encoding error", - Error::EncapsulatedText => "PEM error in encapsulated text", - Error::Label => "PEM type label invalid", - Error::Length => "PEM length invalid", - Error::PreEncapsulationBoundary => "PEM error in pre-encapsulation boundary", - Error::PostEncapsulationBoundary => "PEM error in post-encapsulation boundary", - }) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} - -impl From for Error { - fn from(_: base64ct::Error) -> Error { - Error::Base64 - } -} - -impl From for Error { - fn from(_: base64ct::InvalidLengthError) -> Error { - Error::Length - } -} diff --git a/pem-rfc7468/src/grammar.rs b/pem-rfc7468/src/grammar.rs deleted file mode 100644 index 662f2ac9..00000000 --- a/pem-rfc7468/src/grammar.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Helper functions and rules for enforcing the ABNF grammar for -//! RFC 7468-flavored PEM as described in Section 3. -//! -//! The grammar described below is intended to follow the "ABNF (Strict)" -//! subset of the grammar as described in Section 3 Figure 3. - -use crate::{Error, Result}; -use core::str; - -/// Horizontal tab -pub(crate) const CHAR_HT: u8 = 0x09; - -/// Space -pub(crate) const CHAR_SP: u8 = 0x20; - -/// Carriage return -pub(crate) const CHAR_CR: u8 = 0x0d; - -/// Line feed -pub(crate) const CHAR_LF: u8 = 0x0a; - -/// Does the provided byte match a character allowed in a label? -// TODO(tarcieri): relax this to match the RFC 7468 ABNF -pub(crate) fn is_labelchar(char: u8) -> bool { - matches!(char, b'A'..=b'Z' | CHAR_HT | CHAR_SP) -} - -/// Does the provided byte match the "WSP" ABNF production from Section 3? -/// -/// > The common ABNF production WSP is congruent with "blank"; -/// > a new production W is used for "whitespace" -pub(crate) fn is_wsp(char: u8) -> bool { - matches!(char, CHAR_HT | CHAR_SP) -} - -/// Split a slice beginning with a type label as located in an encapsulation -/// boundary. Returns the label as a `&str`, and slice beginning with the -/// encapsulated text with leading `-----` and newline removed. -/// -/// This implementation follows the rules put forth in Section 2, which are -/// stricter than those found in the ABNF grammar: -/// -/// > Labels are formally case-sensitive, uppercase, and comprised of zero or more -/// > characters; they do not contain consecutive spaces or hyphen-minuses, -/// > nor do they contain spaces or hyphen-minuses at either end. -/// -/// We apply a slightly stricter interpretation: -/// - Labels MAY be empty -/// - Non-empty labels MUST start with an upper-case letter: `'A'..='Z'` -/// - The only allowable characters subsequently are `'A'..='Z'` or WSP. -/// (NOTE: this is an overly strict initial implementation and should be relaxed) -/// - Whitespace MUST NOT contain more than one consecutive WSP character -// TODO(tarcieri): evaluate whether this is too strict; support '-' -pub(crate) fn split_label(bytes: &[u8]) -> Option<(&str, &[u8])> { - let mut n = 0; - - // TODO(tarcieri): handle hyphens in labels as well as spaces - let mut last_was_wsp = false; - - for &char in bytes { - // Validate character - // TODO(tarcieri): unify with `is_labelchar`/`validate_label` - if matches!(char, b'A'..=b'Z') { - last_was_wsp = false; - } else if char == b'-' { - // Possible start of encapsulation boundary delimiter - break; - } else if n != 0 && is_wsp(char) { - // Repeated whitespace disallowed - if last_was_wsp { - return None; - } - - last_was_wsp = true; - } else { - return None; - } - - n += 1; - } - - let (raw_label, rest) = bytes.split_at(n); - let label = str::from_utf8(raw_label).ok()?; - - match rest { - [b'-', b'-', b'-', b'-', b'-', body @ ..] => Some((label, strip_leading_eol(body)?)), - _ => None, - } -} - -/// Strip a newline (`eol`) from the beginning of the provided byte slice. -/// -/// The newline is considered mandatory and a decoding error will occur if it -/// is not present. -/// -/// From RFC 7468 Section 3: -/// > lines are divided with CRLF, CR, or LF. -pub(crate) fn strip_leading_eol(bytes: &[u8]) -> Option<&[u8]> { - match bytes { - [CHAR_LF, rest @ ..] => Some(rest), - [CHAR_CR, CHAR_LF, rest @ ..] => Some(rest), - [CHAR_CR, rest @ ..] => Some(rest), - _ => None, - } -} - -/// Strip a newline (`eol`) from the end of the provided byte slice. -/// -/// The newline is considered mandatory and a decoding error will occur if it -/// is not present. -/// -/// From RFC 7468 Section 3: -/// > lines are divided with CRLF, CR, or LF. -pub(crate) fn strip_trailing_eol(bytes: &[u8]) -> Option<&[u8]> { - match bytes { - [head @ .., CHAR_CR, CHAR_LF] => Some(head), - [head @ .., CHAR_LF] => Some(head), - [head @ .., CHAR_CR] => Some(head), - _ => None, - } -} - -/// Validate that the given bytes are allowed as a PEM type label, i.e. the -/// label encoded in the `BEGIN` and `END` encapsulation boundaries. -pub(crate) fn validate_label(label: &[u8]) -> Result<()> { - // TODO(tarcieri): handle hyphens in labels as well as spaces - let mut last_was_wsp = false; - - for &char in label { - if !is_labelchar(char) { - return Err(Error::Label); - } - - if is_wsp(char) { - // Double sequential whitespace characters disallowed - if last_was_wsp { - return Err(Error::Label); - } - - last_was_wsp = true; - } else { - last_was_wsp = false; - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Empty label is OK. - #[test] - fn split_label_empty() { - let (label, body) = split_label(b"-----\nBODY").unwrap(); - assert_eq!(label, ""); - assert_eq!(body, b"BODY"); - } - - /// Label containing text. - #[test] - fn split_label_with_text() { - let (label, body) = split_label(b"PRIVATE KEY-----\nBODY").unwrap(); - assert_eq!(label, "PRIVATE KEY"); - assert_eq!(body, b"BODY"); - } - - /// Reject labels containing repeated spaces - #[test] - fn split_label_with_repeat_wsp_is_err() { - assert!(split_label(b"PRIVATE KEY-----\nBODY").is_none()); - } - - /// Basic validation of a label - #[test] - fn validate_private_key_label() { - assert_eq!(validate_label(b"PRIVATE KEY"), Ok(())); - } - - /// Reject labels with double spaces - #[test] - fn validate_private_key_label_reject_double_space() { - assert_eq!(validate_label(b"PRIVATE KEY"), Err(Error::Label)); - } -} diff --git a/pem-rfc7468/src/lib.rs b/pem-rfc7468/src/lib.rs deleted file mode 100644 index defd0a80..00000000 --- a/pem-rfc7468/src/lib.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! Pure Rust implementation of PEM Encoding ([RFC 7468]) for PKIX, PKCS, and -//! CMS Structures, a strict subset of the original Privacy-Enhanced Mail encoding -//! intended specifically for use with cryptographic keys, certificates, and other -//! messages. -//! -//! Provides a `no_std`-friendly, constant-time implementation suitable for use with -//! cryptographic private keys. -//! -//! # About -//! -//! Many cryptography-related document formats, such as certificates (PKIX), -//! private and public keys/keypairs (PKCS), and other cryptographic messages (CMS) -//! provide an ASCII encoding which can be traced back to Privacy-Enhanced Mail -//! (PEM) as defined in [RFC 1421], which look like the following: -//! -//! ```text -//! -----BEGIN PRIVATE KEY----- -//! MC4CAQAwBQYDK2VwBCIEIBftnHPp22SewYmmEoMcX8VwI4IHwaqd+9LFPj/15eqF -//! -----END PRIVATE KEY----- -//! ``` -//! -//! However, all of these formats actually implement a text-based encoding that is -//! similar to, but *not* identical with, the legacy PEM encoding as described in -//! [RFC 1421]. -//! -//! For this reason, [RFC 7468] was created to describe a stricter form of -//! "PEM encoding" for use in these applications which codifies the previously -//! de facto rules that most implementations operate by, and makes recommendations -//! to promote interoperability. -//! -//! This crate attempts to implement a strict interpretation of the [RFC 7468] -//! rules, implementing all of the MUSTs and SHOULDs while avoiding the MAYs, -//! and targeting the "ABNF (Strict)" subset of the grammar as described in -//! [RFC 7468 Section 3 Figure 3 (p6)][RFC 7468 p6]. -//! -//! # Implementation notes -//! -//! - Core PEM implementation is `no_std`-friendly and requires no heap allocations. -//! - Avoids use of copies and temporary buffers. -//! - Uses the [`base64ct`] crate to decode/encode Base64 in constant-time. -//! - PEM parser avoids branching on potentially secret data as much as -//! possible. In the happy path, only 1-byte of secret data is potentially -//! branched upon. -//! -//! Note: a forthcoming paper [Util::Lookup: Exploiting key decoding in cryptographic libraries][Util::Lookup] -//! demonstrates how the leakage from non-constant-time PEM parsers can be used -//! to practically extract RSA private keys from SGX enclaves. -//! -//! # Minimum Supported Rust Version -//! -//! This crate requires **Rust 1.51** at a minimum. -//! -//! # Usage -//! -//! ``` -//! # fn main() -> Result<(), Box> { -//! # #[cfg(feature = "alloc")] -//! # { -//! /// Example PEM document -//! /// NOTE: do not actually put private key literals into your source code!!! -//! let example_pem = "\ -//! -----BEGIN PRIVATE KEY----- -//! MC4CAQAwBQYDK2VwBCIEIBftnHPp22SewYmmEoMcX8VwI4IHwaqd+9LFPj/15eqF -//! -----END PRIVATE KEY----- -//! "; -//! -//! // Decode PEM -//! let (type_label, data) = pem_rfc7468::decode_vec(example_pem.as_bytes())?; -//! assert_eq!(type_label, "PRIVATE KEY"); -//! assert_eq!( -//! data, -//! &[ -//! 48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, 23, 237, 156, 115, 233, 219, -//! 100, 158, 193, 137, 166, 18, 131, 28, 95, 197, 112, 35, 130, 7, 193, 170, 157, 251, -//! 210, 197, 62, 63, 245, 229, 234, 133 -//! ] -//! ); -//! -//! // Encode PEM -//! use pem_rfc7468::LineEnding; -//! let encoded_pem = pem_rfc7468::encode_string(type_label, LineEnding::default(), &data)?; -//! assert_eq!(&encoded_pem, example_pem); -//! # } -//! # Ok(()) -//! # } -//! ``` -//! -//! [RFC 1421]: https://datatracker.ietf.org/doc/html/rfc1421 -//! [RFC 7468]: https://datatracker.ietf.org/doc/html/rfc7468 -//! [RFC 7468 p6]: https://datatracker.ietf.org/doc/html/rfc7468#page-6 -//! [Util::Lookup]: https://twitter.com/JanWichelmann/status/1418532480081145857 - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/pem-rfc7468/0.1.1" -)] -#![forbid(unsafe_code, clippy::unwrap_used)] -#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] - -#[cfg(feature = "alloc")] -#[macro_use] -extern crate alloc; -#[cfg(feature = "std")] -extern crate std; - -mod decoder; -mod encoder; -mod error; -mod grammar; - -pub use crate::{ - decoder::decode, - encoder::{encode, encoded_len, LineEnding}, - error::{Error, Result}, -}; - -#[cfg(feature = "alloc")] -pub use crate::{decoder::decode_vec, encoder::encode_string}; - -/// The pre-encapsulation boundary appears before the encapsulated text. -/// -/// From RFC 7468 Section 2: -/// > There are exactly five hyphen-minus (also known as dash) characters ("-") -/// > on both ends of the encapsulation boundaries, no more, no less. -const PRE_ENCAPSULATION_BOUNDARY: &[u8] = b"-----BEGIN "; - -/// The post-encapsulation boundary appears immediately after the encapsulated text. -const POST_ENCAPSULATION_BOUNDARY: &[u8] = b"-----END "; - -/// Delimiter of encapsulation boundaries. -const ENCAPSULATION_BOUNDARY_DELIMITER: &[u8] = b"-----"; - -/// Width at which Base64 must be wrapped. -/// -/// From RFC 7468 Section 2: -/// -/// > Generators MUST wrap the base64-encoded lines so that each line -/// > consists of exactly 64 characters except for the final line, which -/// > will encode the remainder of the data (within the 64-character line -/// > boundary), and they MUST NOT emit extraneous whitespace. Parsers MAY -/// > handle other line sizes. -const BASE64_WRAP_WIDTH: usize = 64; diff --git a/pem-rfc7468/tests/decode.rs b/pem-rfc7468/tests/decode.rs deleted file mode 100644 index 7c42e471..00000000 --- a/pem-rfc7468/tests/decode.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! PEM decoding tests - -#[test] -fn pkcs1_example() { - let pem = include_bytes!("examples/pkcs1.pem"); - let mut buf = [0u8; 2048]; - let (label, decoded) = pem_rfc7468::decode(pem, &mut buf).unwrap(); - assert_eq!(label, "RSA PRIVATE KEY"); - assert_eq!(decoded, include_bytes!("examples/pkcs1.der")); -} - -#[test] -fn pkcs8_example() { - let pem = include_bytes!("examples/pkcs8.pem"); - let mut buf = [0u8; 2048]; - let (label, decoded) = pem_rfc7468::decode(pem, &mut buf).unwrap(); - assert_eq!(label, "PRIVATE KEY"); - assert_eq!(decoded, include_bytes!("examples/pkcs8.der")); -} - -#[test] -fn pkcs8_enc_example() { - let pem = include_bytes!("examples/pkcs8-enc.pem"); - let mut buf = [0u8; 2048]; - let (label, decoded) = pem_rfc7468::decode(pem, &mut buf).unwrap(); - assert_eq!(label, "ENCRYPTED PRIVATE KEY"); - assert_eq!(decoded, include_bytes!("examples/pkcs8-enc.der")); -} - -#[test] -#[cfg(feature = "alloc")] -fn pkcs1_example_with_vec() { - let pem = include_bytes!("examples/pkcs1.pem"); - let (label, decoded) = pem_rfc7468::decode_vec(pem).unwrap(); - assert_eq!(label, "RSA PRIVATE KEY"); - assert_eq!(decoded, include_bytes!("examples/pkcs1.der")); -} - -#[test] -#[cfg(feature = "alloc")] -fn pkcs8_enc_example_with_vec() { - let pem = include_bytes!("examples/pkcs8-enc.pem"); - let (label, decoded) = pem_rfc7468::decode_vec(pem).unwrap(); - assert_eq!(label, "ENCRYPTED PRIVATE KEY"); - assert_eq!(decoded, include_bytes!("examples/pkcs8-enc.der")); -} diff --git a/pem-rfc7468/tests/encode.rs b/pem-rfc7468/tests/encode.rs deleted file mode 100644 index 8f2ac94c..00000000 --- a/pem-rfc7468/tests/encode.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! PEM decoding tests - -#![cfg(feature = "alloc")] - -use pem_rfc7468::LineEnding; - -#[test] -fn pkcs1_example() { - let label = "RSA PRIVATE KEY"; - let bytes = include_bytes!("examples/pkcs1.der"); - let encoded = pem_rfc7468::encode_string(label, LineEnding::LF, bytes).unwrap(); - assert_eq!(&encoded, include_str!("examples/pkcs1.pem")); -} - -#[test] -fn pkcs8_example() { - let label = "PRIVATE KEY"; - let bytes = include_bytes!("examples/pkcs8.der"); - let encoded = pem_rfc7468::encode_string(label, LineEnding::LF, bytes).unwrap(); - assert_eq!(&encoded, include_str!("examples/pkcs8.pem")); -} diff --git a/pem-rfc7468/tests/examples/pkcs1.der b/pem-rfc7468/tests/examples/pkcs1.der deleted file mode 100644 index bbf18768..00000000 Binary files a/pem-rfc7468/tests/examples/pkcs1.der and /dev/null differ diff --git a/pem-rfc7468/tests/examples/pkcs1.pem b/pem-rfc7468/tests/examples/pkcs1.pem deleted file mode 100644 index 3b924f5f..00000000 --- a/pem-rfc7468/tests/examples/pkcs1.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p -78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC -38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ6 -7YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/ -vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+ -CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQABAoIBAH7Mg2LA7bB0EWQh -XiL3SrnZG6BpAHAM9jaQ5RFNjua9z7suP5YUaSpnegg/FopeUuWWjmQHudl8bg5A -ZPgtoLdYoU8XubfUH19I4o1lUXBPVuaeeqn6Yw/HZCjAbSXkVdz8VbesK092ZD/e -0/4V/3irsn5lrMSq0L322yfvYKaRDFxKCF7UMnWrGcHZl6Msbv/OffLRk19uYB7t -4WGhK1zCfKIfgdLJnD0eoI6Q4wU6sJvvpyTe8NDDo8HpdAwNn3YSahSewKp9gHgg -VIQlTZUdsHxM+R+2RUwJZYj9WSTbq+s1nKICUmjQBPnWbrPW963BE5utQPFt3mOe -EWRzdsECgYEA3MBhJC1Okq+u5yrFE8plufdwNvm9fg5uYUYafvdlQiXsFTx+XDGm -FXpuWhP/bheOh1jByzPZ1rvjF57xiZjkIuzcvtePTs/b5fT82K7CydDchkc8qb0W -2dI40h+13e++sUPKYdC9aqjZHzOgl3kOlkDbyRCF3F8mNDujE49rLWcCgYEA0/MU -dX5A6VSDb5K+JCNq8vDaBKNGU8GAr2fpYAhtk/3mXLI+/Z0JN0di9ZgeNhhJr2jN -11OU/2pOButpsgnkIo2y36cOQPf5dQpSgXZke3iNDld3osuLIuPNJn/3C087AtOq -+w4YxZClZLAxiLCqX8SBVrB2IiFCQ70SJ++n8vkCgYEAzmi3rBsNEA1jblVIh1PF -wJhD/bOQ4nBd92iUV8m9jZdl4wl4YX4u/IBI9MMkIG24YIe2VOl7s9Rk5+4/jNg/ -4QQ2998Y6aljxOZJEdZ+3jQELy4m49OhrTRq2ta5t/Z3CMsJTmLe6f9NXWZpr5iK -8iVdHOjtMXxqfYaR2jVNEtsCgYAl9uWUQiAoa037v0I1wO5YQ9IZgJGJUSDWynsg -C4JtPs5zji4ASY+sCipsqWnH8MPKGrC8QClxMr51ONe+30yw78a5jvfbpU9Wqpmq -vOU0xJwnlH1GeMUcY8eMfOFocjG0yOtYeubvBIDLr0/AFzz9WHp+Z69RX7m53nUR -GDlyKQKBgDGZVAbUBiB8rerqNbONBAxfipoa4IJ+ntBrFT2DtoIZNbSzaoK+nVbH -kbWMJycaV5PVOh1lfAiZeWCxQz5RcZh/RS8USnxyMG1j4dP/wLcbdasI8uRaSC6Y -hFHL5HjhLrIo0HRWySS2b2ztBI2FP1M+MaaGFPHDzm2OyZg85yr3 ------END RSA PRIVATE KEY----- diff --git a/pem-rfc7468/tests/examples/pkcs8-enc.der b/pem-rfc7468/tests/examples/pkcs8-enc.der deleted file mode 100644 index 5170c06e..00000000 Binary files a/pem-rfc7468/tests/examples/pkcs8-enc.der and /dev/null differ diff --git a/pem-rfc7468/tests/examples/pkcs8-enc.pem b/pem-rfc7468/tests/examples/pkcs8-enc.pem deleted file mode 100644 index e5d3207a..00000000 --- a/pem-rfc7468/tests/examples/pkcs8-enc.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAh52YLnDfkaiAICCAAw -DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEELLQLXiy79nf9pTPjgr0CSUEQNDN -bHcPS7hxdkIjBcF0AYCeImZ0znQYXSIb/aqVBpiQyIgvzgKwXUG8v1SwNVlbzUFU -syWTcIRpuGqs+IFaeys= ------END ENCRYPTED PRIVATE KEY----- diff --git a/pem-rfc7468/tests/examples/pkcs8.der b/pem-rfc7468/tests/examples/pkcs8.der deleted file mode 100644 index 0cfccc39..00000000 Binary files a/pem-rfc7468/tests/examples/pkcs8.der and /dev/null differ diff --git a/pem-rfc7468/tests/examples/pkcs8.pem b/pem-rfc7468/tests/examples/pkcs8.pem deleted file mode 100644 index 0c0ee10b..00000000 --- a/pem-rfc7468/tests/examples/pkcs8.pem +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIBftnHPp22SewYmmEoMcX8VwI4IHwaqd+9LFPj/15eqF ------END PRIVATE KEY----- diff --git a/pkcs1/CHANGELOG.md b/pkcs1/CHANGELOG.md deleted file mode 100644 index fe003d80..00000000 --- a/pkcs1/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.2.3 (2021-07-26) -### Added -- Support for customizing PEM `LineEnding` ([#553]) - -### Changed -- Bump `pem-rfc7468` dependency to v0.2 ([#552]) - -[#552]: https://github.com/RustCrypto/utils/pull/552 -[#553]: https://github.com/RustCrypto/utils/pull/553 - -## 0.2.2 (2021-07-25) -### Fixed -- `Version` encoder ([#547]) - -[#547]: https://github.com/RustCrypto/utils/pull/547 - -## 0.2.1 (2021-07-25) -### Added -- `Error::Crypto` variant ([#544]) - -[#544]: https://github.com/RustCrypto/utils/pull/544 - -## 0.2.0 (2021-07-25) -### Added -- `From*`/`To*` traits for `RsaPrivateKey`/`RsaPublicKey` ([#540]) - -### Changed -- Use `FromRsa*`/`ToRsa*` traits with `*Document` types ([#541]) - -[#540]: https://github.com/RustCrypto/utils/pull/540 -[#541]: https://github.com/RustCrypto/utils/pull/541 - -## 0.1.1 (2021-07-24) -### Added -- Re-export `der` crate and `der::UIntBytes` ([#537]) - -### Changed -- Replace `Error::{Decode, Encode}` with `Error::Asn1` ([#538]) - -[#537]: https://github.com/RustCrypto/utils/pull/537 -[#538]: https://github.com/RustCrypto/utils/pull/538 - -## 0.1.0 (2021-07-24) [YANKED] -- Initial release diff --git a/pkcs1/Cargo.toml b/pkcs1/Cargo.toml deleted file mode 100644 index 87fd05ab..00000000 --- a/pkcs1/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "pkcs1" -version = "0.2.3" # Also update html_root_url in lib.rs when bumping this -description = """ -Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #1: -RSA Cryptography Specifications Version 2.2 (RFC 8017) -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -repository = "https://github.com/RustCrypto/utils/tree/master/pkcs1" -categories = ["cryptography", "data-structures", "encoding", "no-std", "parser-implementations"] -keywords = ["crypto", "key", "pem", "pkcs", "rsa"] -readme = "README.md" - -[dependencies] -der = { version = "0.4", features = ["bigint", "oid"], path = "../der" } - -# optional dependencies -pem-rfc7468 = { version = "0.2", optional = true, path = "../pem-rfc7468" } -zeroize = { version = "1", optional = true, default-features = false, features = ["alloc"] } - -[dev-dependencies] -hex-literal = "0.3" - -[features] -alloc = ["der/alloc", "zeroize"] -pem = ["alloc", "pem-rfc7468/alloc"] -std = [] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/pkcs1/LICENSE-APACHE b/pkcs1/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/pkcs1/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/pkcs1/LICENSE-MIT b/pkcs1/LICENSE-MIT deleted file mode 100644 index c869ada5..00000000 --- a/pkcs1/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2021 The RustCrypto Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/pkcs1/README.md b/pkcs1/README.md deleted file mode 100644 index 8639512f..00000000 --- a/pkcs1/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# RustCrypto: PKCS#1 (RSA) - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #1: -RSA Cryptography Specifications Version 2.2 ([RFC 8017]). - -[Documentation][docs-link] - -## About - -This crate supports encoding and decoding RSA private and public keys -in either PKCS#1 DER (binary) or PEM (text) formats. - -PEM encoded RSA private keys begin with: - -``` ------BEGIN RSA PRIVATE KEY----- -``` - -PEM encoded RSA public keys begin with: - -``` ------BEGIN RSA PUBLIC KEY----- -``` - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/pkcs1.svg -[crate-link]: https://crates.io/crates/pkcs1 -[docs-image]: https://docs.rs/pkcs1/badge.svg -[docs-link]: https://docs.rs/pkcs1/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/pkcs1/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions - -[//]: # (general links) - -[RFC 8017]: https://datatracker.ietf.org/doc/html/rfc8017 diff --git a/pkcs1/src/document.rs b/pkcs1/src/document.rs deleted file mode 100644 index cfddc9d2..00000000 --- a/pkcs1/src/document.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Serialized DER-encoded documents stored in heap-backed buffers. -// TODO(tarcieri): heapless support? - -pub(crate) mod private_key; -pub(crate) mod public_key; diff --git a/pkcs1/src/document/private_key.rs b/pkcs1/src/document/private_key.rs deleted file mode 100644 index 872be196..00000000 --- a/pkcs1/src/document/private_key.rs +++ /dev/null @@ -1,198 +0,0 @@ -//! PKCS#1 RSA private key document. - -use crate::{error, Error, FromRsaPrivateKey, Result, RsaPrivateKey, ToRsaPrivateKey}; -use alloc::{borrow::ToOwned, vec::Vec}; -use core::{ - convert::{TryFrom, TryInto}, - fmt, -}; -use der::Encodable; -use zeroize::{Zeroize, Zeroizing}; - -#[cfg(feature = "pem")] -use { - crate::{pem, private_key::PEM_TYPE_LABEL, LineEnding}, - alloc::string::String, - core::str::FromStr, -}; - -#[cfg(feature = "std")] -use std::{fs, path::Path, str}; - -/// PKCS#1 `RSA PRIVATE KEY` document. -/// -/// This type provides storage for [`RsaPrivateKey`] encoded as ASN.1 DER -/// with the invariant that the contained-document is "well-formed", i.e. it -/// will parse successfully according to this crate's parsing rules. -#[derive(Clone)] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub struct RsaPrivateKeyDocument(Zeroizing>); - -impl RsaPrivateKeyDocument { - /// Parse the [`RsaPrivateKey`] contained in this [`RsaPrivateKeyDocument`] - pub fn private_key(&self) -> RsaPrivateKey<'_> { - RsaPrivateKey::try_from(self.0.as_ref()).expect("malformed PrivateKeyDocument") - } - - /// Borrow the inner DER encoded bytes. - pub fn as_der(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl FromRsaPrivateKey for RsaPrivateKeyDocument { - fn from_pkcs1_private_key(private_key: RsaPrivateKey<'_>) -> Result { - Ok(Self(Zeroizing::new(private_key.to_vec()?))) - } - - fn from_pkcs1_der(bytes: &[u8]) -> Result { - // Ensure document is well-formed - RsaPrivateKey::try_from(bytes)?; - Ok(Self(Zeroizing::new(bytes.to_owned()))) - } - - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn from_pkcs1_pem(s: &str) -> Result { - let (label, der_bytes) = pem::decode_vec(s.as_bytes())?; - - if label != PEM_TYPE_LABEL { - return Err(pem::Error::Label.into()); - } - - // Ensure document is well-formed - RsaPrivateKey::try_from(der_bytes.as_slice())?; - Ok(Self(Zeroizing::new(der_bytes))) - } - - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs1_der_file(path: &Path) -> Result { - fs::read(path)?.try_into() - } - - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs1_pem_file(path: &Path) -> Result { - Self::from_pkcs1_pem(&Zeroizing::new(fs::read_to_string(path)?)) - } -} - -impl ToRsaPrivateKey for RsaPrivateKeyDocument { - fn to_pkcs1_der(&self) -> Result { - Ok(self.clone()) - } - - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_pkcs1_pem_with_le(&self, line_ending: LineEnding) -> Result> { - let pem_doc = pem::encode_string(PEM_TYPE_LABEL, line_ending, self.as_der())?; - Ok(Zeroizing::new(pem_doc)) - } - - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs1_der_file(&self, path: &Path) -> Result<()> { - write_secret_file(path, self.as_der()) - } - - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs1_pem_file(&self, path: &Path) -> Result<()> { - let pem_doc = self.to_pkcs1_pem()?; - write_secret_file(path, pem_doc.as_bytes()) - } -} - -impl AsRef<[u8]> for RsaPrivateKeyDocument { - fn as_ref(&self) -> &[u8] { - self.as_der() - } -} - -impl From> for RsaPrivateKeyDocument { - fn from(private_key: RsaPrivateKey<'_>) -> RsaPrivateKeyDocument { - RsaPrivateKeyDocument::from(&private_key) - } -} - -impl From<&RsaPrivateKey<'_>> for RsaPrivateKeyDocument { - fn from(private_key: &RsaPrivateKey<'_>) -> RsaPrivateKeyDocument { - private_key - .to_vec() - .ok() - .and_then(|buf| buf.try_into().ok()) - .expect(error::DER_ENCODING_MSG) - } -} - -impl TryFrom<&[u8]> for RsaPrivateKeyDocument { - type Error = Error; - - fn try_from(bytes: &[u8]) -> Result { - RsaPrivateKeyDocument::from_pkcs1_der(bytes) - } -} - -impl TryFrom> for RsaPrivateKeyDocument { - type Error = Error; - - fn try_from(mut bytes: Vec) -> Result { - // Ensure document is well-formed - if let Err(err) = RsaPrivateKey::try_from(bytes.as_slice()) { - bytes.zeroize(); - return Err(err); - } - - Ok(Self(Zeroizing::new(bytes))) - } -} - -impl fmt::Debug for RsaPrivateKeyDocument { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_tuple("RsaPrivateKeyDocument") - .field(&self.private_key()) - .finish() - } -} - -#[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -impl FromStr for RsaPrivateKeyDocument { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::from_pkcs1_pem(s) - } -} - -/// Write a file containing secret data to the filesystem, restricting the -/// file permissions so it's only readable by the owner -#[cfg(all(unix, feature = "std"))] -pub(super) fn write_secret_file(path: impl AsRef, data: &[u8]) -> Result<()> { - use std::{io::Write, os::unix::fs::OpenOptionsExt}; - - /// File permissions for secret data - #[cfg(unix)] - const SECRET_FILE_PERMS: u32 = 0o600; - - fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .mode(SECRET_FILE_PERMS) - .open(path) - .and_then(|mut file| file.write_all(data))?; - - Ok(()) -} - -/// Write a file containing secret data to the filesystem -// TODO(tarcieri): permissions hardening on Windows -#[cfg(all(not(unix), feature = "std"))] -pub(super) fn write_secret_file(path: impl AsRef, data: &[u8]) -> Result<()> { - fs::write(path, data)?; - Ok(()) -} diff --git a/pkcs1/src/document/public_key.rs b/pkcs1/src/document/public_key.rs deleted file mode 100644 index 28071f0a..00000000 --- a/pkcs1/src/document/public_key.rs +++ /dev/null @@ -1,164 +0,0 @@ -//! PKCS#1 RSA public key document. - -use crate::{error, Error, FromRsaPublicKey, Result, RsaPublicKey, ToRsaPublicKey}; -use alloc::{borrow::ToOwned, vec::Vec}; -use core::{ - convert::{TryFrom, TryInto}, - fmt, -}; -use der::Encodable; - -#[cfg(feature = "std")] -use std::{fs, path::Path, str}; - -#[cfg(feature = "pem")] -use { - crate::{pem, public_key::PEM_TYPE_LABEL, LineEnding}, - alloc::string::String, - core::str::FromStr, -}; - -/// PKCS#1 `RSA PUBLIC KEY` document. -/// -/// This type provides storage for [`RsaPublicKey`] encoded as ASN.1 -/// DER with the invariant that the contained-document is "well-formed", i.e. -/// it will parse successfully according to this crate's parsing rules. -#[derive(Clone)] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub struct RsaPublicKeyDocument(Vec); - -impl RsaPublicKeyDocument { - /// Parse the [`RsaPublicKey`] contained in this [`RsaPublicKeyDocument`] - pub fn public_key(&self) -> RsaPublicKey<'_> { - RsaPublicKey::try_from(self.0.as_slice()).expect("malformed PublicKeyDocument") - } - - /// Borrow the inner DER encoded bytes. - pub fn as_der(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl FromRsaPublicKey for RsaPublicKeyDocument { - fn from_pkcs1_public_key(public_key: RsaPublicKey<'_>) -> Result { - Ok(Self(public_key.to_vec()?)) - } - - fn from_pkcs1_der(bytes: &[u8]) -> Result { - // Ensure document is well-formed - RsaPublicKey::try_from(bytes)?; - Ok(Self(bytes.to_owned())) - } - - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn from_pkcs1_pem(s: &str) -> Result { - let (label, der_bytes) = pem::decode_vec(s.as_bytes())?; - - if label != PEM_TYPE_LABEL { - return Err(pem::Error::Label.into()); - } - - // Ensure document is well-formed - RsaPublicKey::try_from(der_bytes.as_slice())?; - Ok(Self(der_bytes)) - } - - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs1_der_file(path: &Path) -> Result { - fs::read(path)?.try_into() - } - - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs1_pem_file(path: &Path) -> Result { - Self::from_pkcs1_pem(&fs::read_to_string(path)?) - } -} - -impl ToRsaPublicKey for RsaPublicKeyDocument { - fn to_pkcs1_der(&self) -> Result { - Ok(self.clone()) - } - - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_pkcs1_pem_with_le(&self, line_ending: LineEnding) -> Result { - Ok(pem::encode_string(PEM_TYPE_LABEL, line_ending, &self.0)?) - } - - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs1_der_file(&self, path: &Path) -> Result<()> { - fs::write(path, self.as_ref())?; - Ok(()) - } - - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs1_pem_file(&self, path: &Path) -> Result<()> { - fs::write(path, self.to_pkcs1_pem()?.as_bytes())?; - Ok(()) - } -} - -impl AsRef<[u8]> for RsaPublicKeyDocument { - fn as_ref(&self) -> &[u8] { - self.as_der() - } -} - -impl From> for RsaPublicKeyDocument { - fn from(public_key: RsaPublicKey<'_>) -> RsaPublicKeyDocument { - RsaPublicKeyDocument::from(&public_key) - } -} - -impl From<&RsaPublicKey<'_>> for RsaPublicKeyDocument { - fn from(public_key: &RsaPublicKey<'_>) -> RsaPublicKeyDocument { - public_key - .to_vec() - .ok() - .and_then(|buf| buf.try_into().ok()) - .expect(error::DER_ENCODING_MSG) - } -} - -impl TryFrom<&[u8]> for RsaPublicKeyDocument { - type Error = Error; - - fn try_from(bytes: &[u8]) -> Result { - RsaPublicKeyDocument::from_pkcs1_der(bytes) - } -} - -impl TryFrom> for RsaPublicKeyDocument { - type Error = Error; - - fn try_from(bytes: Vec) -> Result { - // Ensure document is well-formed - RsaPublicKey::try_from(bytes.as_slice())?; - Ok(Self(bytes)) - } -} - -impl fmt::Debug for RsaPublicKeyDocument { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_tuple("RsaPublicKeyDocument") - .field(&self.public_key()) - .finish() - } -} - -#[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -impl FromStr for RsaPublicKeyDocument { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::from_pkcs1_pem(s) - } -} diff --git a/pkcs1/src/error.rs b/pkcs1/src/error.rs deleted file mode 100644 index 1de30189..00000000 --- a/pkcs1/src/error.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! Error types - -use core::fmt; - -#[cfg(feature = "pem")] -use crate::pem; - -/// Message to display when an `expect`-ed DER encoding error occurs -#[cfg(feature = "alloc")] -pub(crate) const DER_ENCODING_MSG: &str = "DER encoding error"; - -/// Result type -pub type Result = core::result::Result; - -/// Error type -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub enum Error { - /// ASN.1 DER-related errors. - Asn1(der::Error), - - /// Cryptographic errors. - /// - /// These can be used by RSA implementations to signal that a key is - /// invalid for cryptographic reasons. This means the document parsed - /// correctly, but one of the values contained within was invalid, e.g. - /// a number expected to be a prime was not a prime. - Crypto, - - /// File not found error. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - FileNotFound, - - /// I/O errors. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - Io, - - /// PEM encoding errors. - #[cfg(feature = "pem")] - Pem(pem::Error), - - /// Permission denied reading file. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - PermissionDenied, - - /// Version errors - Version, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Asn1(err) => write!(f, "PKCS#1 ASN.1 error: {}", err), - Error::Crypto => f.write_str("PKCS#1 cryptographic error"), - #[cfg(feature = "std")] - Error::FileNotFound => f.write_str("file not found"), - #[cfg(feature = "std")] - Error::Io => f.write_str("I/O error"), - #[cfg(feature = "pem")] - Error::Pem(err) => write!(f, "PKCS#1 {}", err), - Error::Version => f.write_str("PKCS#1 version error"), - #[cfg(feature = "std")] - Error::PermissionDenied => f.write_str("permission denied"), - } - } -} - -#[cfg(feature = "pem")] -impl From for Error { - fn from(err: pem_rfc7468::Error) -> Error { - Error::Pem(err) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} - -impl From for Error { - fn from(err: der::Error) -> Error { - Error::Asn1(err) - } -} - -#[cfg(feature = "std")] -impl From for Error { - fn from(err: std::io::Error) -> Error { - match err.kind() { - std::io::ErrorKind::NotFound => Error::FileNotFound, - std::io::ErrorKind::PermissionDenied => Error::PermissionDenied, - _ => Error::Io, - } - } -} diff --git a/pkcs1/src/lib.rs b/pkcs1/src/lib.rs deleted file mode 100644 index cc486f16..00000000 --- a/pkcs1/src/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #1: -//! -//! RSA Cryptography Specifications Version 2.2 ([RFC 8017]) -//! -//! ## About -//! -//! This crate supports encoding and decoding RSA private and public keys -//! in either PKCS#1 DER (binary) or PEM (text) formats. -//! -//! PEM encoded RSA private keys begin with: -//! -//! ```text -//! -----BEGIN RSA PRIVATE KEY----- -//! ``` -//! -//! PEM encoded RSA public keys begin with: -//! -//! ```text -//! -----BEGIN RSA PUBLIC KEY----- -//! ``` -//! -//! # Minimum Supported Rust Version -//! -//! This crate requires **Rust 1.51** at a minimum. -//! -//! [RFC 8017]: https://tools.ietf.org/html/rfc8017 -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/pkcs1/0.2.3" -)] -#![forbid(unsafe_code, clippy::unwrap_used)] -#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] - -#[cfg(feature = "alloc")] -extern crate alloc; -#[cfg(feature = "std")] -extern crate std; - -mod error; -mod private_key; -mod public_key; -mod traits; -mod version; - -#[cfg(feature = "alloc")] -mod document; - -pub use der::{self, asn1::UIntBytes}; - -pub use self::{ - error::{Error, Result}, - private_key::RsaPrivateKey, - public_key::RsaPublicKey, - traits::{FromRsaPrivateKey, FromRsaPublicKey}, - version::Version, -}; - -#[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -pub use pem_rfc7468::LineEnding; - -#[cfg(feature = "alloc")] -pub use crate::{ - document::{private_key::RsaPrivateKeyDocument, public_key::RsaPublicKeyDocument}, - traits::{ToRsaPrivateKey, ToRsaPublicKey}, -}; - -#[cfg(feature = "pem")] -use pem_rfc7468 as pem; diff --git a/pkcs1/src/private_key.rs b/pkcs1/src/private_key.rs deleted file mode 100644 index 9fad6fe4..00000000 --- a/pkcs1/src/private_key.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! PKCS#1 RSA Private Keys. - -use crate::{Error, Result, RsaPublicKey, Version}; -use core::{convert::TryFrom, fmt}; -use der::{ - asn1::{Any, UIntBytes}, - Decodable, Encodable, Message, -}; - -#[cfg(feature = "alloc")] -use crate::RsaPrivateKeyDocument; - -#[cfg(feature = "pem")] -use { - crate::{pem, LineEnding}, - alloc::string::String, - zeroize::Zeroizing, -}; - -/// Type label for PEM-encoded private keys. -#[cfg(feature = "pem")] -pub(crate) const PEM_TYPE_LABEL: &str = "RSA PRIVATE KEY"; - -/// PKCS#1 RSA Private Keys as defined in [RFC 8017 Appendix 1.2]. -/// -/// ASN.1 structure containing a serialized RSA private key: -/// -/// ```text -/// RSAPrivateKey ::= SEQUENCE { -/// version Version, -/// modulus INTEGER, -- n -/// publicExponent INTEGER, -- e -/// privateExponent INTEGER, -- d -/// prime1 INTEGER, -- p -/// prime2 INTEGER, -- q -/// exponent1 INTEGER, -- d mod (p-1) -/// exponent2 INTEGER, -- d mod (q-1) -/// coefficient INTEGER, -- (inverse of q) mod p -/// otherPrimeInfos OtherPrimeInfos OPTIONAL -/// } -/// ``` -/// -/// [RFC 8017 Appendix 1.2]: https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.2 -#[derive(Clone)] -pub struct RsaPrivateKey<'a> { - /// Version number: `two-prime` or `multi` - pub version: Version, - - /// `n`: RSA modulus - pub modulus: UIntBytes<'a>, - - /// `e`: RSA public exponent - pub public_exponent: UIntBytes<'a>, - - /// `d`: RSA private exponent - pub private_exponent: UIntBytes<'a>, - - /// `p`: first prime factor of `n` - pub prime1: UIntBytes<'a>, - - /// `q`: Second prime factor of `n` - pub prime2: UIntBytes<'a>, - - /// First exponent: `d mod (p-1)` - pub exponent1: UIntBytes<'a>, - - /// Second exponent: `d mod (q-1)` - pub exponent2: UIntBytes<'a>, - - /// CRT coefficient: `(inverse of q) mod p` - pub coefficient: UIntBytes<'a>, -} - -impl<'a> RsaPrivateKey<'a> { - /// Get the public key that corresponds to this [`RsaPrivateKey`]. - pub fn public_key(&self) -> RsaPublicKey<'a> { - RsaPublicKey { - modulus: self.modulus, - public_exponent: self.public_exponent, - } - } - - /// Encode this [`RsaPrivateKey`] as ASN.1 DER. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - pub fn to_der(&self) -> RsaPrivateKeyDocument { - self.into() - } - - /// Encode this [`RsaPrivateKey`] as PEM-encoded ASN.1 DER. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem(&self) -> Result> { - self.to_pem_with_le(LineEnding::default()) - } - - /// Encode this [`RsaPrivateKey`] as PEM-encoded ASN.1 DER using the given - /// [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem_with_le(&self, line_ending: LineEnding) -> Result> { - let pem_doc = pem::encode_string(PEM_TYPE_LABEL, line_ending, self.to_der().as_ref())?; - Ok(Zeroizing::new(pem_doc)) - } -} - -impl<'a> From> for RsaPublicKey<'a> { - fn from(private_key: RsaPrivateKey<'a>) -> RsaPublicKey<'a> { - private_key.public_key() - } -} - -impl<'a> From<&RsaPrivateKey<'a>> for RsaPublicKey<'a> { - fn from(private_key: &RsaPrivateKey<'a>) -> RsaPublicKey<'a> { - private_key.public_key() - } -} - -impl<'a> TryFrom<&'a [u8]> for RsaPrivateKey<'a> { - type Error = Error; - - fn try_from(bytes: &'a [u8]) -> Result { - Ok(Self::from_der(bytes)?) - } -} - -impl<'a> TryFrom> for RsaPrivateKey<'a> { - type Error = der::Error; - - fn try_from(any: Any<'a>) -> der::Result> { - any.sequence(|decoder| { - Ok(Self { - version: decoder.decode()?, - modulus: decoder.decode()?, - public_exponent: decoder.decode()?, - private_exponent: decoder.decode()?, - prime1: decoder.decode()?, - prime2: decoder.decode()?, - exponent1: decoder.decode()?, - exponent2: decoder.decode()?, - coefficient: decoder.decode()?, - }) - }) - } -} - -impl<'a> Message<'a> for RsaPrivateKey<'a> { - fn fields(&self, f: F) -> der::Result - where - F: FnOnce(&[&dyn Encodable]) -> der::Result, - { - f(&[ - &self.version, - &self.modulus, - &self.public_exponent, - &self.private_exponent, - &self.prime1, - &self.prime2, - &self.exponent1, - &self.exponent2, - &self.coefficient, - ]) - } -} - -impl<'a> fmt::Debug for RsaPrivateKey<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RsaPrivateKey") - .field("version", &self.version) - .field("modulus", &self.modulus) - .field("public_exponent", &self.public_exponent) - .field("private_exponent", &"...") - .field("prime1", &"...") - .field("prime2", &"...") - .field("exponent1", &"...") - .field("exponent2", &"...") - .field("coefficient", &"...") - .finish() // TODO: use `finish_non_exhaustive` when stable - } -} diff --git a/pkcs1/src/public_key.rs b/pkcs1/src/public_key.rs deleted file mode 100644 index 22b8b655..00000000 --- a/pkcs1/src/public_key.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! PKCS#1 RSA Public Keys. - -use crate::{Error, Result}; -use core::convert::TryFrom; -use der::{ - asn1::{Any, UIntBytes}, - Decodable, Encodable, Message, -}; - -#[cfg(feature = "alloc")] -use crate::RsaPublicKeyDocument; - -#[cfg(feature = "pem")] -use { - crate::{pem, LineEnding}, - alloc::string::String, -}; - -/// Type label for PEM-encoded private keys. -#[cfg(feature = "pem")] -pub(crate) const PEM_TYPE_LABEL: &str = "RSA PUBLIC KEY"; - -/// PKCS#1 RSA Public Keys as defined in [RFC 8017 Appendix 1.1]. -/// -/// ASN.1 structure containing a serialized RSA public key: -/// -/// ```text -/// RSAPublicKey ::= SEQUENCE { -/// modulus INTEGER, -- n -/// publicExponent INTEGER -- e -/// } -/// ``` -/// -/// [RFC 8017 Appendix 1.1]: https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.1 -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct RsaPublicKey<'a> { - /// `n`: RSA modulus - pub modulus: UIntBytes<'a>, - - /// `e`: RSA public exponent - pub public_exponent: UIntBytes<'a>, -} - -impl<'a> RsaPublicKey<'a> { - /// Encode this [`RsaPublicKey`] as ASN.1 DER. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - pub fn to_der(self) -> RsaPublicKeyDocument { - self.into() - } - - /// Encode this [`RsaPublicKey`] as PEM-encoded ASN.1 DER. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem(self) -> Result { - self.to_pem_with_le(LineEnding::default()) - } - - /// Encode this [`RsaPublicKey`] as PEM-encoded ASN.1 DER with the given - /// [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem_with_le(self, line_ending: LineEnding) -> Result { - Ok(pem::encode_string( - PEM_TYPE_LABEL, - line_ending, - self.to_der().as_ref(), - )?) - } -} - -impl<'a> TryFrom<&'a [u8]> for RsaPublicKey<'a> { - type Error = Error; - - fn try_from(bytes: &'a [u8]) -> Result { - Ok(Self::from_der(bytes)?) - } -} - -impl<'a> TryFrom> for RsaPublicKey<'a> { - type Error = der::Error; - - fn try_from(any: Any<'a>) -> der::Result> { - any.sequence(|decoder| { - Ok(Self { - modulus: decoder.decode()?, - public_exponent: decoder.decode()?, - }) - }) - } -} - -impl<'a> Message<'a> for RsaPublicKey<'a> { - fn fields(&self, f: F) -> der::Result - where - F: FnOnce(&[&dyn Encodable]) -> der::Result, - { - f(&[&self.modulus, &self.public_exponent]) - } -} diff --git a/pkcs1/src/traits.rs b/pkcs1/src/traits.rs deleted file mode 100644 index 4644e385..00000000 --- a/pkcs1/src/traits.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! Traits for parsing objects from PKCS#1 encoded documents - -use crate::{Result, RsaPrivateKey, RsaPublicKey}; -use core::convert::TryFrom; - -#[cfg(feature = "alloc")] -use crate::{RsaPrivateKeyDocument, RsaPublicKeyDocument}; - -#[cfg(feature = "pem")] -use {crate::LineEnding, alloc::string::String}; - -#[cfg(feature = "std")] -use std::path::Path; - -#[cfg(any(feature = "pem", feature = "std"))] -use zeroize::Zeroizing; - -/// Parse an [`RsaPrivateKey`] from a PKCS#1-encoded document. -pub trait FromRsaPrivateKey: Sized { - /// Parse the [`RsaPrivateKey`] from a PKCS#1-encoded document. - fn from_pkcs1_private_key(private_key: RsaPrivateKey<'_>) -> Result; - - /// Deserialize PKCS#1 private key from ASN.1 DER-encoded data - /// (binary format). - fn from_pkcs1_der(bytes: &[u8]) -> Result { - Self::from_pkcs1_private_key(RsaPrivateKey::try_from(bytes)?) - } - - /// Deserialize PKCS#1-encoded private key from PEM. - /// - /// Keys in this format begin with the following: - /// - /// ```text - /// -----BEGIN RSA PRIVATE KEY----- - /// ``` - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn from_pkcs1_pem(s: &str) -> Result { - RsaPrivateKeyDocument::from_pkcs1_pem(s) - .and_then(|doc| Self::from_pkcs1_private_key(doc.private_key())) - } - - /// Load PKCS#1 private key from an ASN.1 DER-encoded file on the local - /// filesystem (binary format). - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs1_der_file(path: &Path) -> Result { - RsaPrivateKeyDocument::read_pkcs1_der_file(path) - .and_then(|doc| Self::from_pkcs1_private_key(doc.private_key())) - } - - /// Load PKCS#1 private key from a PEM-encoded file on the local filesystem. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs1_pem_file(path: &Path) -> Result { - RsaPrivateKeyDocument::read_pkcs1_pem_file(path) - .and_then(|doc| Self::from_pkcs1_private_key(doc.private_key())) - } -} - -/// Parse a [`RsaPublicKey`] from a PKCS#1-encoded document. -pub trait FromRsaPublicKey: Sized { - /// Parse [`RsaPublicKey`] into a [`RsaPublicKey`]. - fn from_pkcs1_public_key(public_key: RsaPublicKey<'_>) -> Result; - - /// Deserialize object from ASN.1 DER-encoded [`RsaPublicKey`] - /// (binary format). - fn from_pkcs1_der(bytes: &[u8]) -> Result { - Self::from_pkcs1_public_key(RsaPublicKey::try_from(bytes)?) - } - - /// Deserialize PEM-encoded [`RsaPublicKey`]. - /// - /// Keys in this format begin with the following: - /// - /// ```text - /// -----BEGIN RSA PUBLIC KEY----- - /// ``` - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn from_pkcs1_pem(s: &str) -> Result { - RsaPublicKeyDocument::from_pkcs1_pem(s) - .and_then(|doc| Self::from_pkcs1_public_key(doc.public_key())) - } - - /// Load [`RsaPublicKey`] from an ASN.1 DER-encoded file on the local - /// filesystem (binary format). - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs1_der_file(path: &Path) -> Result { - RsaPublicKeyDocument::read_pkcs1_der_file(path) - .and_then(|doc| Self::from_pkcs1_public_key(doc.public_key())) - } - - /// Load [`RsaPublicKey`] from a PEM-encoded file on the local filesystem. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs1_pem_file(path: &Path) -> Result { - RsaPublicKeyDocument::read_pkcs1_pem_file(path) - .and_then(|doc| Self::from_pkcs1_public_key(doc.public_key())) - } -} - -/// Serialize a [`RsaPrivateKey`] to a PKCS#1 encoded document. -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub trait ToRsaPrivateKey { - /// Serialize a [`RsaPrivateKeyDocument`] containing a PKCS#1-encoded private key. - fn to_pkcs1_der(&self) -> Result; - - /// Serialize this private key as PEM-encoded PKCS#1. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_pkcs1_pem(&self) -> Result> { - self.to_pkcs1_pem_with_le(LineEnding::default()) - } - - /// Serialize this private key as PEM-encoded PKCS#1 with the given [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_pkcs1_pem_with_le(&self, line_ending: LineEnding) -> Result> { - self.to_pkcs1_der()?.to_pkcs1_pem_with_le(line_ending) - } - - /// Write ASN.1 DER-encoded PKCS#1 private key to the given path. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs1_der_file(&self, path: &Path) -> Result<()> { - self.to_pkcs1_der()?.write_pkcs1_der_file(path) - } - - /// Write ASN.1 DER-encoded PKCS#1 private key to the given path. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs1_pem_file(&self, path: &Path) -> Result<()> { - self.to_pkcs1_der()?.write_pkcs1_pem_file(path) - } -} - -/// Serialize a [`RsaPublicKey`] to a PKCS#1-encoded document. -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub trait ToRsaPublicKey { - /// Serialize a [`RsaPublicKeyDocument`] containing a PKCS#1-encoded public key. - fn to_pkcs1_der(&self) -> Result; - - /// Serialize this public key as PEM-encoded PKCS#1. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_pkcs1_pem(&self) -> Result { - self.to_pkcs1_pem_with_le(LineEnding::default()) - } - - /// Serialize this public key as PEM-encoded PKCS#1 with the given line ending. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_pkcs1_pem_with_le(&self, line_ending: LineEnding) -> Result { - self.to_pkcs1_der()?.to_pkcs1_pem_with_le(line_ending) - } - - /// Write ASN.1 DER-encoded public key to the given path. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs1_der_file(&self, path: &Path) -> Result<()> { - self.to_pkcs1_der()?.write_pkcs1_der_file(path) - } - - /// Write ASN.1 DER-encoded public key to the given path. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs1_pem_file(&self, path: &Path) -> Result<()> { - self.to_pkcs1_der()?.write_pkcs1_pem_file(path) - } -} diff --git a/pkcs1/src/version.rs b/pkcs1/src/version.rs deleted file mode 100644 index 74f82f41..00000000 --- a/pkcs1/src/version.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! PKCS#1 version identifier. - -use crate::Error; -use core::convert::{TryFrom, TryInto}; -use der::{asn1::Any, Encodable, Encoder, Tag, Tagged}; - -/// Version identifier for PKCS#1 documents as defined in -/// [RFC 8017 Appendix 1.2]. -/// -/// > version is the version number, for compatibility with future -/// > revisions of this document. It SHALL be 0 for this version of the -/// > document, unless multi-prime is used; in which case, it SHALL be 1. -/// -/// ```text -/// Version ::= INTEGER { two-prime(0), multi(1) } -/// (CONSTRAINED BY -/// {-- version must be multi if otherPrimeInfos present --}) -/// ``` -/// -/// [RFC 8017 Appendix 1.2]: https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.2 -#[derive(Clone, Debug, Copy, Eq, PartialEq, PartialOrd, Ord)] -pub enum Version { - /// Denotes a `two-prime` key - TwoPrime = 0, - - /// Denotes a `multi` (i.e. multi-prime) key - Multi = 1, -} - -impl From for u8 { - fn from(version: Version) -> Self { - version as u8 - } -} - -impl TryFrom for Version { - type Error = Error; - fn try_from(byte: u8) -> Result { - match byte { - 0 => Ok(Version::TwoPrime), - 1 => Ok(Version::Multi), - _ => Err(Error::Version), - } - } -} - -impl<'a> TryFrom> for Version { - type Error = der::Error; - fn try_from(any: Any<'a>) -> der::Result { - u8::try_from(any)? - .try_into() - .map_err(|_| der::ErrorKind::Value { tag: Tag::Integer }.into()) - } -} - -impl Encodable for Version { - fn encoded_len(&self) -> der::Result { - der::Length::ONE.for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> der::Result<()> { - u8::from(*self).encode(encoder) - } -} - -impl Tagged for Version { - const TAG: Tag = Tag::Integer; -} diff --git a/pkcs1/tests/examples/rsa2048-priv.der b/pkcs1/tests/examples/rsa2048-priv.der deleted file mode 100644 index bbf18768..00000000 Binary files a/pkcs1/tests/examples/rsa2048-priv.der and /dev/null differ diff --git a/pkcs1/tests/examples/rsa2048-priv.pem b/pkcs1/tests/examples/rsa2048-priv.pem deleted file mode 100644 index 3b924f5f..00000000 --- a/pkcs1/tests/examples/rsa2048-priv.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p -78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC -38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ6 -7YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/ -vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+ -CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQABAoIBAH7Mg2LA7bB0EWQh -XiL3SrnZG6BpAHAM9jaQ5RFNjua9z7suP5YUaSpnegg/FopeUuWWjmQHudl8bg5A -ZPgtoLdYoU8XubfUH19I4o1lUXBPVuaeeqn6Yw/HZCjAbSXkVdz8VbesK092ZD/e -0/4V/3irsn5lrMSq0L322yfvYKaRDFxKCF7UMnWrGcHZl6Msbv/OffLRk19uYB7t -4WGhK1zCfKIfgdLJnD0eoI6Q4wU6sJvvpyTe8NDDo8HpdAwNn3YSahSewKp9gHgg -VIQlTZUdsHxM+R+2RUwJZYj9WSTbq+s1nKICUmjQBPnWbrPW963BE5utQPFt3mOe -EWRzdsECgYEA3MBhJC1Okq+u5yrFE8plufdwNvm9fg5uYUYafvdlQiXsFTx+XDGm -FXpuWhP/bheOh1jByzPZ1rvjF57xiZjkIuzcvtePTs/b5fT82K7CydDchkc8qb0W -2dI40h+13e++sUPKYdC9aqjZHzOgl3kOlkDbyRCF3F8mNDujE49rLWcCgYEA0/MU -dX5A6VSDb5K+JCNq8vDaBKNGU8GAr2fpYAhtk/3mXLI+/Z0JN0di9ZgeNhhJr2jN -11OU/2pOButpsgnkIo2y36cOQPf5dQpSgXZke3iNDld3osuLIuPNJn/3C087AtOq -+w4YxZClZLAxiLCqX8SBVrB2IiFCQ70SJ++n8vkCgYEAzmi3rBsNEA1jblVIh1PF -wJhD/bOQ4nBd92iUV8m9jZdl4wl4YX4u/IBI9MMkIG24YIe2VOl7s9Rk5+4/jNg/ -4QQ2998Y6aljxOZJEdZ+3jQELy4m49OhrTRq2ta5t/Z3CMsJTmLe6f9NXWZpr5iK -8iVdHOjtMXxqfYaR2jVNEtsCgYAl9uWUQiAoa037v0I1wO5YQ9IZgJGJUSDWynsg -C4JtPs5zji4ASY+sCipsqWnH8MPKGrC8QClxMr51ONe+30yw78a5jvfbpU9Wqpmq -vOU0xJwnlH1GeMUcY8eMfOFocjG0yOtYeubvBIDLr0/AFzz9WHp+Z69RX7m53nUR -GDlyKQKBgDGZVAbUBiB8rerqNbONBAxfipoa4IJ+ntBrFT2DtoIZNbSzaoK+nVbH -kbWMJycaV5PVOh1lfAiZeWCxQz5RcZh/RS8USnxyMG1j4dP/wLcbdasI8uRaSC6Y -hFHL5HjhLrIo0HRWySS2b2ztBI2FP1M+MaaGFPHDzm2OyZg85yr3 ------END RSA PRIVATE KEY----- diff --git a/pkcs1/tests/examples/rsa2048-pub.der b/pkcs1/tests/examples/rsa2048-pub.der deleted file mode 100644 index e75261c3..00000000 Binary files a/pkcs1/tests/examples/rsa2048-pub.der and /dev/null differ diff --git a/pkcs1/tests/examples/rsa2048-pub.pem b/pkcs1/tests/examples/rsa2048-pub.pem deleted file mode 100644 index f2f72d7f..00000000 --- a/pkcs1/tests/examples/rsa2048-pub.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN RSA PUBLIC KEY----- -MIIBCgKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p78MZ -GsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC38Ff -wBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ67YGS -0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J -9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+CV+j -cFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQAB ------END RSA PUBLIC KEY----- diff --git a/pkcs1/tests/examples/rsa4096-priv.der b/pkcs1/tests/examples/rsa4096-priv.der deleted file mode 100644 index b154e597..00000000 Binary files a/pkcs1/tests/examples/rsa4096-priv.der and /dev/null differ diff --git a/pkcs1/tests/examples/rsa4096-priv.pem b/pkcs1/tests/examples/rsa4096-priv.pem deleted file mode 100644 index 9156bf0d..00000000 --- a/pkcs1/tests/examples/rsa4096-priv.pem +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIJKQIBAAKCAgEAp6dFcoEeomF+Sehb1zDd4w8QP32I7j92XlQNPdmTu7C6FAAC -hZ0LQIl0NmN/WLgo6nTfgyFjQHf5nUqi1UyjdYUu9ZdmHTcTzh7ztP1qjiICOORn -ZoosfuOGHSISrmoevd+oi2LfEPa8957/SsKY+yVj3xuHZDga+bH7DM0IXgJrCtn2 -chojUXfQOWtIdUrUp1JCJQqHO/L25+48dd1hPjZbpPMhCmzGa5Ci+j92LKaIQIe2 -v4Fh6xRIGfD1cvIfbI4nPnDUWjZbiygZznNGE8wjsBMpoXkB8XB4QDhh9UxSoFHi -pYx1wtnYAJG7mAihBsH37LQDThUFi+7HJcX5GdYuqiNLYmKNNGxgu5GecIUdqzhX -Hm8O12NBKfmU6jaP7nNz397AREXrykf6IO0VQKhgyUi6vJjaWRyh3i4uJVQO+bfL -NT9gITuBSkXTWe+puBHu/wjGWZO/ioXCv+qqftXmtD4YrmBEZM5flhUBNufQn4sk -+tQ9eHARjPp7wkh1UG67wyG5d+CGGupQEoYgEh8LOUqc3QpCQRoTUMB3DZddcbAK -kENiQMlnoMOlwgoPbed/Pyyv2pTtAUPB9uNPc+DKwnnu63xjdyOisCbIKALhpK66 -qIRt+Y55GUmHc+DU8xmVb03jqtAO+5oUfWazrBoB01ss+0jUALDnqA3JdVECAwEA -AQKCAgEAn+MJeyMiuQ+rZgbAF6CV6+ZAw5wQC87gLyOPoU2v846eV1aPESftRDYS -a5BGMbEn7Dlbs+4SfrgsiNJWKn+1X+2NFFC35OLS839XQmNvzG8omWNSLVtXBggs -rfoBwO6ZtNDpJ006mS4Gl0y+AWlGhjVpYqwZWf2b1EfluZaMBUPfG/E0dCrzRc2y -+h+TcbDUz2HGjRbWU9jpmdT9OhbPl4o1qkDoYM3OCWVd2LTPGdQUGx6SrV5RqOSl -wn+nRWEdkOSdDpKCIiq28SZkPhx3V4gW/OO5jzIdJUnylKRw34RTRGvzb5hd8l7Y -/en98wc/sncn30jp4fxwVrx4llCQt4UBJkBkYsglMFHvhONO48POuPlsZYw4vkVV -jS9k4p0iM1BVX8Hvoo7B9K+1ukCA8JqGzcNTjBrXyXLm16NhLmhFupr73xnwkGDR -p3neljXi0vjgxRC6JMbESzDJvfr4W+kXrsXUOvqxqjrdM8yD2pPKxpIY9qNutH8Z -nVQkyV/Z7Xsei+KuqmQzsickExbCDueSZQzrSL/WNERrGdKGtOoXIkmNoaNpcyEO -w4JHUaWAjZqu9ZxEnhmlB3z+yhJr2ajdSZZWHU4ns2Cf+CxbGyHmJ4RdRJYbM7h1 -1cT6n/NX72vjNklp4TN8kbKaB7mpE83kDOLVUwyQDnN1FoXmVDECggEBANAhOnlC -W2ZbcZEYRIiT7DJ1YA9j2/hbd/To6Z7zAvboJZYEj23Kdy3mu/ESTbhLCv5hsDqG -BKsAee1T8zBHl60Bs4xE/ielpF43hIOoBLVqSpZ/SPAahm5yHmfkyaEEivaJJ/qk -PWqF2T579wdNunl1Y/yr4SMJt2ZTxtthTcIxzFVtnyWsSEGgLTHN8wFbISMH+dDH -n+tdOVbOU8yPoWUb5gdh8Z90ZySJ6vnyFUCfOZVud6ghg/H3K7L+3fG5+/xK2J6k -RYCd29W9WVJ3mQwL6TZvuy7PewV8wcPcj7d7+EVtB7vJWzwYFfSOYrgUaMPU2dls -D0jasEmTvo2R7eUCggEBAM42xoEFIqvl1kZfNusTfaO56kpfHSfGYUcp645eLly4 -jj7xpHOiGUS2ZVez3CzkYuS/NEbLSZADflZysXBcuugbZbr5Z6Jm3Bjv6A9Nu/4a -WQYyBc4pQ8rfQhzOdK9wY/0ag688Oa+EUl9ZvcH/VIFfUq/R6NSGKyw2VPbPqD3A -jiqdUrn4M8ZGr3aURn38X31617RBiV/Lf/vtUmMksBVKFYI/UQfIlUjt3LYdpTCM -bMg01KDBbfpsodZ7YaZWd+sXGc0SXQ7w24gC+3bPwXV3vLJRCuKU4b+KkXOiuFwW -prUIyY8tdwt/PeSNnnIMU+JjaAtX5xCUEAFXRVcGUv0CggEAdItGzQPlXmmyLEdk -iP4b8x1azwNh965we4m42DLH5C6WbWzcS+Rl3CQp9ZIER0BuRYe6QOsuzfqUS9sI -gG52doBPZCp2DwloAwIfiAGbsWJ1pdRcqWaRBGOOtyqb5ThAAFFJO8agRXfx8FVG -PKa/1qdvd9tfVFlqgzhCUDIqcqWj/+pEhbn1NBpXdF4YxxeadJ1QvCIsYIVxSDR9 -JD0BaTa4FkY4IMvzvbglBhUS5X7DpfOXuWQbGHEJ3U9uRJ+ahOn8ZskhyiWbJhLD -Y7Ro1SAOVVc3f7za7HWxotVs/JfErEujWvojxoDOOoVIrj9vcslLu74QyQD8WhcL -Swb+KQKCAQB1yk4LBp7uZ8PEwMCC+MgsjJbq0ne575RDbQuTb/K1neoKxEa2kmIy -oKk0tpVOw0pF9X3r7lTfwU8aHDuEvkM5L+UlLy9mUbDpQahhjXqTxAMUCeDNCT8j -E/IUuE1opR9IRSvxHcqpmkDfHEjLFojzuTpnGdUQCG+Cuqo/rRAh7eqHJwRJHCCe -4mN5rWqyrkTxTQkHeuP4Zyp9AeusnBlEn+O3WWl0s7uqQ8xt7nMcTyoYFi1aggLL -J+Atvp5hwESRccmYHSQw053ijCmNjVCpQ7LyfF5mXLqyiXlZ/xml6H5jLFjNwx+b -3pvBAK//31DPIQ8eY6CmFJ0r1ujRs9gVAoIBAQCMVXxINei0BmYGpdwlXbw+tfFY -bHMomIyOJCQD+Vde+w0oASwGNLckF2itciBJOCkG1jWvyx0qBb3/yAX+ZwOJt/qQ -1l+Ur7wgCNm8DTzO5CXfxyQCBQYDyfV57oUQ7MaTrG3TKDa24V49xSA2ahukr8kd -Pkik1nJMpCRD9IA+OxJjSwQ4Vy2OE7xy8agoU7jZ/KYZnXqQq2TZ5595OsKH+aGQ -EQgqUmjyCTMtVNmNbhkDCQf9nmuC7xJGd7oIrWeXpEIhPQl6NRxQoIko3XMtaymm -z2cG2vXyD3j4cXD7J5QDxSIbXlxwwrqg5ppy4NHzJn29jJvd/yivqS83yY02 ------END RSA PRIVATE KEY----- diff --git a/pkcs1/tests/examples/rsa4096-pub.der b/pkcs1/tests/examples/rsa4096-pub.der deleted file mode 100644 index d8b7c7d7..00000000 Binary files a/pkcs1/tests/examples/rsa4096-pub.der and /dev/null differ diff --git a/pkcs1/tests/examples/rsa4096-pub.pem b/pkcs1/tests/examples/rsa4096-pub.pem deleted file mode 100644 index d60e8ebf..00000000 --- a/pkcs1/tests/examples/rsa4096-pub.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN RSA PUBLIC KEY----- -MIICCgKCAgEAp6dFcoEeomF+Sehb1zDd4w8QP32I7j92XlQNPdmTu7C6FAAChZ0L -QIl0NmN/WLgo6nTfgyFjQHf5nUqi1UyjdYUu9ZdmHTcTzh7ztP1qjiICOORnZoos -fuOGHSISrmoevd+oi2LfEPa8957/SsKY+yVj3xuHZDga+bH7DM0IXgJrCtn2choj -UXfQOWtIdUrUp1JCJQqHO/L25+48dd1hPjZbpPMhCmzGa5Ci+j92LKaIQIe2v4Fh -6xRIGfD1cvIfbI4nPnDUWjZbiygZznNGE8wjsBMpoXkB8XB4QDhh9UxSoFHipYx1 -wtnYAJG7mAihBsH37LQDThUFi+7HJcX5GdYuqiNLYmKNNGxgu5GecIUdqzhXHm8O -12NBKfmU6jaP7nNz397AREXrykf6IO0VQKhgyUi6vJjaWRyh3i4uJVQO+bfLNT9g -ITuBSkXTWe+puBHu/wjGWZO/ioXCv+qqftXmtD4YrmBEZM5flhUBNufQn4sk+tQ9 -eHARjPp7wkh1UG67wyG5d+CGGupQEoYgEh8LOUqc3QpCQRoTUMB3DZddcbAKkENi -QMlnoMOlwgoPbed/Pyyv2pTtAUPB9uNPc+DKwnnu63xjdyOisCbIKALhpK66qIRt -+Y55GUmHc+DU8xmVb03jqtAO+5oUfWazrBoB01ss+0jUALDnqA3JdVECAwEAAQ== ------END RSA PUBLIC KEY----- diff --git a/pkcs1/tests/private_key.rs b/pkcs1/tests/private_key.rs deleted file mode 100644 index 94647eff..00000000 --- a/pkcs1/tests/private_key.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! PKCS#1 private key tests - -use core::convert::TryFrom; -use hex_literal::hex; -use pkcs1::{RsaPrivateKey, Version}; - -#[cfg(feature = "pem")] -use pkcs1::RsaPrivateKeyDocument; - -/// RSA-2048 PKCS#1 private key encoded as ASN.1 DER. -/// -/// Note: this key is extracted from the corresponding `rsa2048-priv.der` -/// example key in the `pkcs8` crate. -const RSA_2048_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv.der"); - -/// RSA-4096 PKCS#1 private key encoded as ASN.1 DER -const RSA_4096_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa4096-priv.der"); - -/// RSA-2048 PKCS#1 private key encoded as PEM -#[cfg(feature = "pem")] -const RSA_2048_PEM_EXAMPLE: &str = include_str!("examples/rsa2048-priv.pem"); - -/// RSA-4096 PKCS#1 private key encoded as PEM -#[cfg(feature = "pem")] -const RSA_4096_PEM_EXAMPLE: &str = include_str!("examples/rsa4096-priv.pem"); - -#[test] -fn decode_rsa2048_der() { - let key = RsaPrivateKey::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - assert_eq!(key.version, Version::TwoPrime); - - // Extracted using: - // $ openssl asn1parse -in tests/examples/rsa2048-priv.pem - assert_eq!(key.modulus.as_bytes(), hex!("B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEAC89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F")); - assert_eq!(key.public_exponent.as_bytes(), hex!("010001")); - assert_eq!(key.private_exponent.as_bytes(), hex!("7ECC8362C0EDB0741164215E22F74AB9D91BA06900700CF63690E5114D8EE6BDCFBB2E3F9614692A677A083F168A5E52E5968E6407B9D97C6E0E4064F82DA0B758A14F17B9B7D41F5F48E28D6551704F56E69E7AA9FA630FC76428C06D25E455DCFC55B7AC2B4F76643FDED3FE15FF78ABB27E65ACC4AAD0BDF6DB27EF60A6910C5C4A085ED43275AB19C1D997A32C6EFFCE7DF2D1935F6E601EEDE161A12B5CC27CA21F81D2C99C3D1EA08E90E3053AB09BEFA724DEF0D0C3A3C1E9740C0D9F76126A149EC0AA7D8078205484254D951DB07C4CF91FB6454C096588FD5924DBABEB359CA2025268D004F9D66EB3D6F7ADC1139BAD40F16DDE639E11647376C1")); - assert_eq!(key.prime1.as_bytes(), hex!("DCC061242D4E92AFAEE72AC513CA65B9F77036F9BD7E0E6E61461A7EF7654225EC153C7E5C31A6157A6E5A13FF6E178E8758C1CB33D9D6BBE3179EF18998E422ECDCBED78F4ECFDBE5F4FCD8AEC2C9D0DC86473CA9BD16D9D238D21FB5DDEFBEB143CA61D0BD6AA8D91F33A097790E9640DBC91085DC5F26343BA3138F6B2D67")); - assert_eq!(key.prime2.as_bytes(), hex!("D3F314757E40E954836F92BE24236AF2F0DA04A34653C180AF67E960086D93FDE65CB23EFD9D09374762F5981E361849AF68CDD75394FF6A4E06EB69B209E4228DB2DFA70E40F7F9750A528176647B788D0E5777A2CB8B22E3CD267FF70B4F3B02D3AAFB0E18C590A564B03188B0AA5FC48156B07622214243BD1227EFA7F2F9")); - assert_eq!(key.exponent1.as_bytes(), hex!("CE68B7AC1B0D100D636E55488753C5C09843FDB390E2705DF7689457C9BD8D9765E30978617E2EFC8048F4C324206DB86087B654E97BB3D464E7EE3F8CD83FE10436F7DF18E9A963C4E64911D67EDE34042F2E26E3D3A1AD346ADAD6B9B7F67708CB094E62DEE9FF4D5D6669AF988AF2255D1CE8ED317C6A7D8691DA354D12DB")); - assert_eq!(key.exponent2.as_bytes(), hex!("25F6E5944220286B4DFBBF4235C0EE5843D2198091895120D6CA7B200B826D3ECE738E2E00498FAC0A2A6CA969C7F0C3CA1AB0BC40297132BE7538D7BEDF4CB0EFC6B98EF7DBA54F56AA99AABCE534C49C27947D4678C51C63C78C7CE1687231B4C8EB587AE6EF0480CBAF4FC0173CFD587A7E67AF515FB9B9DE751118397229")); - assert_eq!(key.coefficient.as_bytes(), hex!("31995406D406207CADEAEA35B38D040C5F8A9A1AE0827E9ED06B153D83B6821935B4B36A82BE9D56C791B58C27271A5793D53A1D657C08997960B1433E5171987F452F144A7C72306D63E1D3FFC0B71B75AB08F2E45A482E988451CBE478E12EB228D07456C924B66F6CED048D853F533E31A68614F1C3CE6D8EC9983CE72AF7")); -} - -#[test] -fn decode_rsa4096_der() { - let key = RsaPrivateKey::try_from(RSA_4096_DER_EXAMPLE).unwrap(); - assert_eq!(key.version, Version::TwoPrime); - - // Extracted using: - // $ openssl asn1parse -in tests/examples/rsa4096-priv.pem - assert_eq!(key.modulus.as_bytes(), hex!("A7A74572811EA2617E49E85BD730DDE30F103F7D88EE3F765E540D3DD993BBB0BA140002859D0B40897436637F58B828EA74DF8321634077F99D4AA2D54CA375852EF597661D3713CE1EF3B4FD6A8E220238E467668A2C7EE3861D2212AE6A1EBDDFA88B62DF10F6BCF79EFF4AC298FB2563DF1B8764381AF9B1FB0CCD085E026B0AD9F6721A235177D0396B48754AD4A75242250A873BF2F6E7EE3C75DD613E365BA4F3210A6CC66B90A2FA3F762CA6884087B6BF8161EB144819F0F572F21F6C8E273E70D45A365B8B2819CE734613CC23B01329A17901F17078403861F54C52A051E2A58C75C2D9D80091BB9808A106C1F7ECB4034E15058BEEC725C5F919D62EAA234B62628D346C60BB919E70851DAB38571E6F0ED7634129F994EA368FEE7373DFDEC04445EBCA47FA20ED1540A860C948BABC98DA591CA1DE2E2E25540EF9B7CB353F60213B814A45D359EFA9B811EEFF08C65993BF8A85C2BFEAAA7ED5E6B43E18AE604464CE5F96150136E7D09F8B24FAD43D7870118CFA7BC24875506EBBC321B977E0861AEA50128620121F0B394A9CDD0A42411A1350C0770D975D71B00A90436240C967A0C3A5C20A0F6DE77F3F2CAFDA94ED0143C1F6E34F73E0CAC279EEEB7C637723A2B026C82802E1A4AEBAA8846DF98E7919498773E0D4F319956F4DE3AAD00EFB9A147D66B3AC1A01D35B2CFB48D400B0E7A80DC97551")); - assert_eq!(key.public_exponent.as_bytes(), hex!("010001")); - assert_eq!(key.private_exponent.as_bytes(), hex!("9FE3097B2322B90FAB6606C017A095EBE640C39C100BCEE02F238FA14DAFF38E9E57568F1127ED4436126B904631B127EC395BB3EE127EB82C88D2562A7FB55FED8D1450B7E4E2D2F37F5742636FCC6F289963522D5B5706082CADFA01C0EE99B4D0E9274D3A992E06974CBE01694686356962AC1959FD9BD447E5B9968C0543DF1BF134742AF345CDB2FA1F9371B0D4CF61C68D16D653D8E999D4FD3A16CF978A35AA40E860CDCE09655DD8B4CF19D4141B1E92AD5E51A8E4A5C27FA745611D90E49D0E9282222AB6F126643E1C77578816FCE3B98F321D2549F294A470DF8453446BF36F985DF25ED8FDE9FDF3073FB27727DF48E9E1FC7056BC78965090B7850126406462C8253051EF84E34EE3C3CEB8F96C658C38BE45558D2F64E29D223350555FC1EFA28EC1F4AFB5BA4080F09A86CDC3538C1AD7C972E6D7A3612E6845BA9AFBDF19F09060D1A779DE9635E2D2F8E0C510BA24C6C44B30C9BDFAF85BE917AEC5D43AFAB1AA3ADD33CC83DA93CAC69218F6A36EB47F199D5424C95FD9ED7B1E8BE2AEAA6433B227241316C20EE792650CEB48BFD634446B19D286B4EA1722498DA1A36973210EC3824751A5808D9AAEF59C449E19A5077CFECA126BD9A8DD4996561D4E27B3609FF82C5B1B21E627845D44961B33B875D5C4FA9FF357EF6BE3364969E1337C91B29A07B9A913CDE40CE2D5530C900E73751685E65431")); - assert_eq!(key.prime1.as_bytes(), hex!("D0213A79425B665B719118448893EC3275600F63DBF85B77F4E8E99EF302F6E82596048F6DCA772DE6BBF1124DB84B0AFE61B03A8604AB0079ED53F3304797AD01B38C44FE27A5A45E378483A804B56A4A967F48F01A866E721E67E4C9A1048AF68927FAA43D6A85D93E7BF7074DBA797563FCABE12309B76653C6DB614DC231CC556D9F25AC4841A02D31CDF3015B212307F9D0C79FEB5D3956CE53CC8FA1651BE60761F19F74672489EAF9F215409F39956E77A82183F1F72BB2FEDDF1B9FBFC4AD89EA445809DDBD5BD595277990C0BE9366FBB2ECF7B057CC1C3DC8FB77BF8456D07BBC95B3C1815F48E62B81468C3D4D9D96C0F48DAB04993BE8D91EDE5")); - assert_eq!(key.prime2.as_bytes(), hex!("CE36C6810522ABE5D6465F36EB137DA3B9EA4A5F1D27C6614729EB8E5E2E5CB88E3EF1A473A21944B66557B3DC2CE462E4BF3446CB4990037E5672B1705CBAE81B65BAF967A266DC18EFE80F4DBBFE1A59063205CE2943CADF421CCE74AF7063FD1A83AF3C39AF84525F59BDC1FF54815F52AFD1E8D4862B2C3654F6CFA83DC08E2A9D52B9F833C646AF7694467DFC5F7D7AD7B441895FCB7FFBED526324B0154A15823F5107C89548EDDCB61DA5308C6CC834D4A0C16DFA6CA1D67B61A65677EB1719CD125D0EF0DB8802FB76CFC17577BCB2510AE294E1BF8A9173A2B85C16A6B508C98F2D770B7F3DE48D9E720C53E263680B57E7109410015745570652FD")); - assert_eq!(key.exponent1.as_bytes(), hex!("748B46CD03E55E69B22C476488FE1BF31D5ACF0361F7AE707B89B8D832C7E42E966D6CDC4BE465DC2429F5920447406E4587BA40EB2ECDFA944BDB08806E7676804F642A760F096803021F88019BB16275A5D45CA9669104638EB72A9BE538400051493BC6A04577F1F055463CA6BFD6A76F77DB5F54596A83384250322A72A5A3FFEA4485B9F5341A57745E18C7179A749D50BC222C60857148347D243D016936B816463820CBF3BDB825061512E57EC3A5F397B9641B187109DD4F6E449F9A84E9FC66C921CA259B2612C363B468D5200E5557377FBCDAEC75B1A2D56CFC97C4AC4BA35AFA23C680CE3A8548AE3F6F72C94BBBBE10C900FC5A170B4B06FE29")); - assert_eq!(key.exponent2.as_bytes(), hex!("75CA4E0B069EEE67C3C4C0C082F8C82C8C96EAD277B9EF94436D0B936FF2B59DEA0AC446B6926232A0A934B6954EC34A45F57DEBEE54DFC14F1A1C3B84BE43392FE5252F2F6651B0E941A8618D7A93C4031409E0CD093F2313F214B84D68A51F48452BF11DCAA99A40DF1C48CB1688F3B93A6719D510086F82BAAA3FAD1021EDEA872704491C209EE26379AD6AB2AE44F14D09077AE3F8672A7D01EBAC9C19449FE3B7596974B3BBAA43CC6DEE731C4F2A18162D5A8202CB27E02DBE9E61C0449171C9981D2430D39DE28C298D8D50A943B2F27C5E665CBAB2897959FF19A5E87E632C58CDC31F9BDE9BC100AFFFDF50CF210F1E63A0A6149D2BD6E8D1B3D815")); - assert_eq!(key.coefficient.as_bytes(), hex!("8C557C4835E8B4066606A5DC255DBC3EB5F1586C7328988C8E242403F9575EFB0D28012C0634B7241768AD722049382906D635AFCB1D2A05BDFFC805FE670389B7FA90D65F94AFBC2008D9BC0D3CCEE425DFC72402050603C9F579EE8510ECC693AC6DD32836B6E15E3DC520366A1BA4AFC91D3E48A4D6724CA42443F4803E3B12634B0438572D8E13BC72F1A82853B8D9FCA6199D7A90AB64D9E79F793AC287F9A19011082A5268F209332D54D98D6E19030907FD9E6B82EF124677BA08AD6797A442213D097A351C50A08928DD732D6B29A6CF6706DAF5F20F78F87170FB279403C5221B5E5C70C2BAA0E69A72E0D1F3267DBD8C9BDDFF28AFA92F37C98D36")); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_rsa_2048_pem() { - let pkcs1_doc: RsaPrivateKeyDocument = RSA_2048_PEM_EXAMPLE.parse().unwrap(); - assert_eq!(pkcs1_doc.as_ref(), RSA_2048_DER_EXAMPLE); - - // Ensure `RsaPrivateKeyDocument` parses successfully - let pk = RsaPrivateKey::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - assert_eq!( - pkcs1_doc.private_key().modulus.as_bytes(), - pk.modulus.as_bytes() - ); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_rsa_4096_pem() { - let pkcs1_doc: RsaPrivateKeyDocument = RSA_4096_PEM_EXAMPLE.parse().unwrap(); - assert_eq!(pkcs1_doc.as_ref(), RSA_4096_DER_EXAMPLE); - - // Ensure `RsaPrivateKeyDocument` parses successfully - let pk = RsaPrivateKey::try_from(RSA_4096_DER_EXAMPLE).unwrap(); - assert_eq!( - pkcs1_doc.private_key().modulus.as_bytes(), - pk.modulus.as_bytes() - ); -} - -#[test] -fn private_key_to_public_key() { - let private_key = RsaPrivateKey::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - let public_key = private_key.public_key(); - - // Extracted using: - // $ openssl asn1parse -in tests/examples/rsa2048-pub.pem - assert_eq!(public_key.modulus.as_bytes(), hex!("B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEAC89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F")); - assert_eq!(public_key.public_exponent.as_bytes(), hex!("010001")); -} diff --git a/pkcs1/tests/public_key.rs b/pkcs1/tests/public_key.rs deleted file mode 100644 index 13e1d015..00000000 --- a/pkcs1/tests/public_key.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! PKCS#1 public key tests - -use core::convert::TryFrom; -use hex_literal::hex; -use pkcs1::RsaPublicKey; - -#[cfg(feature = "pem")] -use pkcs1::RsaPublicKeyDocument; - -/// RSA-2048 PKCS#1 public key encoded as ASN.1 DER. -/// -/// Note: this key is extracted from the corresponding `rsa2048-priv.der` -/// example key in the `pkcs8` crate. -const RSA_2048_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-pub.der"); - -/// RSA-4096 PKCS#1 public key encoded as ASN.1 DER -const RSA_4096_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa4096-pub.der"); - -/// RSA-2048 PKCS#1 public key encoded as PEM -#[cfg(feature = "pem")] -const RSA_2048_PEM_EXAMPLE: &str = include_str!("examples/rsa2048-pub.pem"); - -/// RSA-4096 PKCS#1 public key encoded as PEM -#[cfg(feature = "pem")] -const RSA_4096_PEM_EXAMPLE: &str = include_str!("examples/rsa4096-pub.pem"); - -#[test] -fn decode_rsa2048_der() { - let key = RsaPublicKey::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - - // Extracted using: - // $ openssl asn1parse -in tests/examples/rsa2048-pub.pem - assert_eq!(key.modulus.as_bytes(), hex!("B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEAC89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F")); - assert_eq!(key.public_exponent.as_bytes(), hex!("010001")); -} - -#[test] -fn decode_rsa4096_der() { - let key = RsaPublicKey::try_from(RSA_4096_DER_EXAMPLE).unwrap(); - - // Extracted using: - // $ openssl asn1parse -in tests/examples/rsa4096-pub.pem - assert_eq!(key.modulus.as_bytes(), hex!("A7A74572811EA2617E49E85BD730DDE30F103F7D88EE3F765E540D3DD993BBB0BA140002859D0B40897436637F58B828EA74DF8321634077F99D4AA2D54CA375852EF597661D3713CE1EF3B4FD6A8E220238E467668A2C7EE3861D2212AE6A1EBDDFA88B62DF10F6BCF79EFF4AC298FB2563DF1B8764381AF9B1FB0CCD085E026B0AD9F6721A235177D0396B48754AD4A75242250A873BF2F6E7EE3C75DD613E365BA4F3210A6CC66B90A2FA3F762CA6884087B6BF8161EB144819F0F572F21F6C8E273E70D45A365B8B2819CE734613CC23B01329A17901F17078403861F54C52A051E2A58C75C2D9D80091BB9808A106C1F7ECB4034E15058BEEC725C5F919D62EAA234B62628D346C60BB919E70851DAB38571E6F0ED7634129F994EA368FEE7373DFDEC04445EBCA47FA20ED1540A860C948BABC98DA591CA1DE2E2E25540EF9B7CB353F60213B814A45D359EFA9B811EEFF08C65993BF8A85C2BFEAAA7ED5E6B43E18AE604464CE5F96150136E7D09F8B24FAD43D7870118CFA7BC24875506EBBC321B977E0861AEA50128620121F0B394A9CDD0A42411A1350C0770D975D71B00A90436240C967A0C3A5C20A0F6DE77F3F2CAFDA94ED0143C1F6E34F73E0CAC279EEEB7C637723A2B026C82802E1A4AEBAA8846DF98E7919498773E0D4F319956F4DE3AAD00EFB9A147D66B3AC1A01D35B2CFB48D400B0E7A80DC97551")); - assert_eq!(key.public_exponent.as_bytes(), hex!("010001")); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_rsa_2048_pem() { - let pkcs1_doc: RsaPublicKeyDocument = RSA_2048_PEM_EXAMPLE.parse().unwrap(); - assert_eq!(pkcs1_doc.as_ref(), RSA_2048_DER_EXAMPLE); - - // Ensure `RsaPublicKeyDocument` parses successfully - let pk = RsaPublicKey::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - assert_eq!( - pkcs1_doc.public_key().modulus.as_bytes(), - pk.modulus.as_bytes() - ); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_rsa_4096_pem() { - let pkcs1_doc: RsaPublicKeyDocument = RSA_4096_PEM_EXAMPLE.parse().unwrap(); - assert_eq!(pkcs1_doc.as_ref(), RSA_4096_DER_EXAMPLE); - - // Ensure `RsaPublicKeyDocument` parses successfully - let pk = RsaPublicKey::try_from(RSA_4096_DER_EXAMPLE).unwrap(); - assert_eq!( - pkcs1_doc.public_key().modulus.as_bytes(), - pk.modulus.as_bytes() - ); -} diff --git a/pkcs5/CHANGELOG.md b/pkcs5/CHANGELOG.md deleted file mode 100644 index 99b99c3f..00000000 --- a/pkcs5/CHANGELOG.md +++ /dev/null @@ -1,48 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.3.0 (2021-06-07) -### Changed -- Bump `der` crate dependency to v0.4 ([#490]) -- Bump `spki` crate dependency to v0.4 ([#491]) - -[#490]: https://github.com/RustCrypto/utils/pull/490 -[#491]: https://github.com/RustCrypto/utils/pull/491 - -## 0.2.2 (2021-05-26) -### Added -- `scrypt` support as specified in RFC 7914 ([#434], [#436], [#437]) - -[#434]: https://github.com/RustCrypto/utils/pull/434 -[#436]: https://github.com/RustCrypto/utils/pull/436 -[#437]: https://github.com/RustCrypto/utils/pull/437 - -## 0.2.1 (2021-04-29) -### Changed -- Bump `aes` to v0.7 ([#388]) -- Bump `block-modes` to v0.8 ([#388]) -- Bump `hmac` to v0.11 ([#388]) -- Bump `pbkdf2` to v0.8 ([#388]) - -[#388]: https://github.com/RustCrypto/utils/pull/388 - -## 0.2.0 (2021-03-22) -### Changed -- Bump `der` to v0.3 ([#354]) -- Bump `spki` to v0.3 ([#355]) - -[#354]: https://github.com/RustCrypto/utils/pull/354 -[#355]: https://github.com/RustCrypto/utils/pull/355 - -## 0.1.1 (2021-02-23) -### Added -- Encryption support ([#293], [#295]) - -[#293]: https://github.com/RustCrypto/utils/pull/293 -[#295]: https://github.com/RustCrypto/utils/pull/295 - -## 0.1.0 (2021-02-20) -- Initial release diff --git a/pkcs5/Cargo.toml b/pkcs5/Cargo.toml deleted file mode 100644 index df580953..00000000 --- a/pkcs5/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "pkcs5" -version = "0.3.0" # Also update html_root_url in lib.rs when bumping this -description = """ -Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #5: -Password-Based Cryptography Specification Version 2.1 (RFC 8018) -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -repository = "https://github.com/RustCrypto/utils/tree/master/pkcs5" -categories = ["cryptography", "data-structures", "encoding", "no-std"] -keywords = ["crypto", "key", "pkcs", "password"] -readme = "README.md" - -[dependencies] -der = { version = "0.4", features = ["oid"], path = "../der" } -spki = { version = "0.4", path = "../spki" } - -# optional dependencies -aes = { version = "0.7.4", optional = true } -block-modes = { version = "0.8", optional = true, default-features = false } -hmac = { version = "0.11", optional = true, default-features = false } -pbkdf2 = { version = "0.8", optional = true, default-features = false } -scrypt = { version = "0.7", optional = true, default-features = false } -sha2 = { version = "0.9", optional = true, default-features = false } - -[dev-dependencies] -hex-literal = "0.3" - -[features] -alloc = [] -pbes2 = ["aes", "block-modes", "hmac", "pbkdf2", "scrypt", "sha2"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/pkcs5/LICENSE-APACHE b/pkcs5/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/pkcs5/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/pkcs5/LICENSE-MIT b/pkcs5/LICENSE-MIT deleted file mode 100644 index c869ada5..00000000 --- a/pkcs5/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2021 The RustCrypto Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/pkcs5/README.md b/pkcs5/README.md deleted file mode 100644 index 6c00951b..00000000 --- a/pkcs5/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# RustCrypto: PKCS#5 - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #5: -Password-Based Cryptography Specification Version 2.1 ([RFC 8018]). - -[Documentation][docs-link] - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/pkcs5.svg -[crate-link]: https://crates.io/crates/pkcs5 -[docs-image]: https://docs.rs/pkcs5/badge.svg -[docs-link]: https://docs.rs/pkcs5/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/pkcs5/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions - -[//]: # (general links) - -[RFC 8018]: https://tools.ietf.org/html/rfc8018 diff --git a/pkcs5/src/lib.rs b/pkcs5/src/lib.rs deleted file mode 100644 index d8349a27..00000000 --- a/pkcs5/src/lib.rs +++ /dev/null @@ -1,218 +0,0 @@ -//! Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #5: -//! -//! Password-Based Cryptography Specification Version 2.1 ([RFC 8018]) -//! -//! # Minimum Supported Rust Version -//! -//! This crate requires **Rust 1.51** at a minimum. -//! -//! # Usage -//! -//! The main API for this crate is the [`EncryptionScheme`] enum, which impls -//! the [`Decodable`][`der::Decodable`] and [`Encodable`] traits from the -//! [`der`] crate, and can be used for decoding/encoding PKCS#5 -//! [`AlgorithmIdentifier`] fields. -//! -//! [RFC 8018]: https://tools.ietf.org/html/rfc8018 - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/pkcs5/0.3.0" -)] -#![forbid(unsafe_code, clippy::unwrap_used)] -#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] - -#[cfg(all(feature = "alloc", feature = "pbes2"))] -extern crate alloc; - -pub mod pbes1; -pub mod pbes2; - -pub use der::{self, asn1::ObjectIdentifier, Error}; -pub use spki::AlgorithmIdentifier; - -use core::{ - convert::{TryFrom, TryInto}, - fmt, -}; -use der::{asn1::Any, message, Encodable, Encoder, Length}; - -#[cfg(all(feature = "alloc", feature = "pbes2"))] -use alloc::vec::Vec; - -/// Cryptographic errors -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct CryptoError; - -impl fmt::Display for CryptoError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("PKCS#5 cryptographic error") - } -} - -/// Supported PKCS#5 password-based encryption schemes. -#[derive(Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -#[allow(clippy::large_enum_variant)] -pub enum EncryptionScheme<'a> { - /// Password-Based Encryption Scheme 1 as defined in [RFC 8018 Section 6.1]. - /// - /// [RFC 8018 Section 6.1]: https://tools.ietf.org/html/rfc8018#section-6.1 - Pbes1(pbes1::Parameters), - - /// Password-Based Encryption Scheme 2 as defined in [RFC 8018 Section 6.2]. - /// - /// [RFC 8018 Section 6.2]: https://tools.ietf.org/html/rfc8018#section-6.2 - Pbes2(pbes2::Parameters<'a>), -} - -impl<'a> EncryptionScheme<'a> { - /// Attempt to decrypt the given ciphertext, allocating and returning a - /// byte vector containing the plaintext. - #[cfg(all(feature = "alloc", feature = "pbes2"))] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - #[cfg_attr(docsrs, doc(cfg(feature = "pbes2")))] - pub fn decrypt( - &self, - password: impl AsRef<[u8]>, - ciphertext: &[u8], - ) -> Result, CryptoError> { - match self { - Self::Pbes2(params) => params.decrypt(password, ciphertext), - _ => Err(CryptoError), - } - } - - /// Attempt to decrypt the given ciphertext in-place using a key derived - /// from the provided password and this scheme's parameters. - /// - /// Returns an error if the algorithm specified in this scheme's parameters - /// is unsupported, or if the ciphertext is malformed (e.g. not a multiple - /// of a block mode's padding) - #[cfg(feature = "pbes2")] - #[cfg_attr(docsrs, doc(cfg(feature = "pbes2")))] - pub fn decrypt_in_place<'b>( - &self, - password: impl AsRef<[u8]>, - buffer: &'b mut [u8], - ) -> Result<&'b [u8], CryptoError> { - match self { - Self::Pbes2(params) => params.decrypt_in_place(password, buffer), - _ => Err(CryptoError), - } - } - - /// Encrypt the given plaintext, allocating and returning a vector - /// containing the ciphertext. - #[cfg(all(feature = "alloc", feature = "pbes2"))] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - #[cfg_attr(docsrs, doc(cfg(feature = "pbes2")))] - pub fn encrypt( - &self, - password: impl AsRef<[u8]>, - plaintext: &[u8], - ) -> Result, CryptoError> { - match self { - Self::Pbes2(params) => params.encrypt(password, plaintext), - _ => Err(CryptoError), - } - } - - /// Encrypt the given ciphertext in-place using a key derived from the - /// provided password and this scheme's parameters. - #[cfg(feature = "pbes2")] - #[cfg_attr(docsrs, doc(cfg(feature = "pbes2")))] - pub fn encrypt_in_place<'b>( - &self, - password: impl AsRef<[u8]>, - buffer: &'b mut [u8], - pos: usize, - ) -> Result<&'b [u8], CryptoError> { - match self { - Self::Pbes2(params) => params.encrypt_in_place(password, buffer, pos), - _ => Err(CryptoError), - } - } - - /// Get the [`ObjectIdentifier`] (a.k.a OID) for this algorithm. - pub fn oid(&self) -> ObjectIdentifier { - match self { - Self::Pbes1(params) => params.oid(), - Self::Pbes2(_) => pbes2::PBES2_OID, - } - } - - /// Get [`pbes1::Parameters`] if it is the selected algorithm. - pub fn pbes1(&self) -> Option<&pbes1::Parameters> { - match self { - Self::Pbes1(params) => Some(params), - _ => None, - } - } - - /// Get [`pbes2::Parameters`] if it is the selected algorithm. - pub fn pbes2(&self) -> Option<&pbes2::Parameters<'a>> { - match self { - Self::Pbes2(params) => Some(params), - _ => None, - } - } -} - -impl<'a> TryFrom<&'a [u8]> for EncryptionScheme<'a> { - type Error = Error; - - fn try_from(bytes: &'a [u8]) -> der::Result> { - AlgorithmIdentifier::try_from(bytes).and_then(TryInto::try_into) - } -} - -impl<'a> TryFrom> for EncryptionScheme<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> der::Result> { - AlgorithmIdentifier::try_from(any).and_then(TryInto::try_into) - } -} - -impl<'a> TryFrom> for EncryptionScheme<'a> { - type Error = Error; - - fn try_from(alg: AlgorithmIdentifier<'a>) -> der::Result> { - match alg.oid { - pbes2::PBES2_OID => pbes2::Parameters::try_from(alg.parameters_any()?).map(Into::into), - _ => pbes1::Parameters::try_from(alg).map(Into::into), - } - } -} - -impl<'a> From for EncryptionScheme<'a> { - fn from(params: pbes1::Parameters) -> EncryptionScheme<'a> { - Self::Pbes1(params) - } -} - -impl<'a> From> for EncryptionScheme<'a> { - fn from(params: pbes2::Parameters<'a>) -> EncryptionScheme<'a> { - Self::Pbes2(params) - } -} - -impl<'a> Encodable for EncryptionScheme<'a> { - fn encoded_len(&self) -> der::Result { - match self { - Self::Pbes1(pbes1) => pbes1.encoded_len(), - Self::Pbes2(pbes2) => message::encoded_len(&[&pbes2::PBES2_OID, pbes2]), - } - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> der::Result<()> { - match self { - Self::Pbes1(pbes1) => pbes1.encode(encoder), - Self::Pbes2(pbes2) => encoder.message(&[&pbes2::PBES2_OID, pbes2]), - } - } -} diff --git a/pkcs5/src/pbes1.rs b/pkcs5/src/pbes1.rs deleted file mode 100644 index 8683e7ea..00000000 --- a/pkcs5/src/pbes1.rs +++ /dev/null @@ -1,249 +0,0 @@ -//! Password-Based Encryption Scheme 1 as defined in [RFC 8018 Section 6.1]. -//! -//! [RFC 8018 Section 6.1]: https://tools.ietf.org/html/rfc8018#section-6.1 - -use crate::{AlgorithmIdentifier, Error}; -use core::convert::{TryFrom, TryInto}; -use der::{ - asn1::{Any, ObjectIdentifier, OctetString}, - message, Encodable, Encoder, ErrorKind, Header, Length, Result, Tag, -}; - -/// `pbeWithMD2AndDES-CBC` Object Identifier (OID). -pub const PBE_WITH_MD2_AND_DES_CBC_OID: ObjectIdentifier = - ObjectIdentifier::new("1.2.840.113549.1.5.1"); - -/// `pbeWithMD2AndRC2-CBC` Object Identifier (OID). -pub const PBE_WITH_MD2_AND_RC2_CBC_OID: ObjectIdentifier = - ObjectIdentifier::new("1.2.840.113549.1.5.4"); - -/// `pbeWithMD5AndDES-CBC` Object Identifier (OID). -pub const PBE_WITH_MD5_AND_DES_CBC_OID: ObjectIdentifier = - ObjectIdentifier::new("1.2.840.113549.1.5.3"); - -/// `pbeWithMD5AndRC2-CBC` Object Identifier (OID). -pub const PBE_WITH_MD5_AND_RC2_CBC_OID: ObjectIdentifier = - ObjectIdentifier::new("1.2.840.113549.1.5.6"); - -/// `pbeWithSHA1AndDES-CBC` Object Identifier (OID). -pub const PBE_WITH_SHA1_AND_DES_CBC_OID: ObjectIdentifier = - ObjectIdentifier::new("1.2.840.113549.1.5.10"); - -/// `pbeWithSHA1AndRC2-CBC` Object Identifier (OID). -pub const PBE_WITH_SHA1_AND_RC2_CBC_OID: ObjectIdentifier = - ObjectIdentifier::new("1.2.840.113549.1.5.11"); - -/// Length of a PBES1 salt (as defined in the `PBEParameter` ASN.1 message). -pub const SALT_LENGTH: usize = 8; - -/// Password-Based Encryption Scheme 1 parameters as defined in [RFC 8018 Appendix A.3]. -/// -/// ```text -/// PBEParameter ::= SEQUENCE { -/// salt OCTET STRING (SIZE(8)), -/// iterationCount INTEGER } -/// ``` -/// -/// Note that this struct additionally stores an [`EncryptionScheme`] parameter -/// parsed from the [`ObjectIdentifier`]. -/// -/// [RFC 8018 Appendix A.3]: https://tools.ietf.org/html/rfc8018#appendix-A.3 -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Parameters { - /// Encryption scheme - pub encryption: EncryptionScheme, - - /// Salt value - pub salt: [u8; SALT_LENGTH], - - /// Iteration count - pub iteration_count: u16, -} - -impl Parameters { - /// Get the [`ObjectIdentifier`] (a.k.a OID) for this algorithm. - pub fn oid(&self) -> ObjectIdentifier { - self.encryption.oid() - } -} - -impl<'a> TryFrom> for Parameters { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result { - AlgorithmIdentifier::try_from(any).and_then(TryInto::try_into) - } -} - -impl<'a> TryFrom> for Parameters { - type Error = Error; - - fn try_from(alg: AlgorithmIdentifier<'a>) -> Result { - // Ensure that we have a supported PBES1 algorithm identifier - let encryption = EncryptionScheme::try_from(alg.oid) - .map_err(|_| der::Tag::ObjectIdentifier.value_error())?; - - alg.parameters_any()?.sequence(|params| { - let salt = params - .octet_string()? - .as_bytes() - .try_into() - .map_err(|_| der::Tag::OctetString.value_error())?; - - let iteration_count = params.decode()?; - - Ok(Self { - encryption, - salt, - iteration_count, - }) - }) - } -} - -impl Encodable for Parameters { - fn encoded_len(&self) -> Result { - self.header()?.encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - self.header()?.encode(encoder)?; - let inner_len = self.inner_len()?; - - encoder.sequence(inner_len, |nested_encoder| { - nested_encoder.oid(self.encryption.oid())?; - nested_encoder.message(&[&self.salt_string()?, &self.iteration_count])?; - Ok(()) - }) - } -} - -impl Parameters { - /// Get the DER [`Header`] - fn header(&self) -> Result
{ - Header::new(Tag::Sequence, self.inner_len()?) - } - - /// Get the inner length of the encoded sequence - fn inner_len(&self) -> Result { - let oid_len = self.encryption.oid().encoded_len()?; - let params_len = message::encoded_len(&[&self.salt_string()?, &self.iteration_count])?; - oid_len + params_len - } - - /// Get an [`OctetString`] wrapper for the salt - fn salt_string(&self) -> Result> { - OctetString::new(&self.salt) - } -} - -/// Password-Based Encryption Scheme 1 algorithms as defined in [RFC 8018 Appendix A.3]. -/// -/// [RFC 8018 Appendix A.3]: https://tools.ietf.org/html/rfc8018#appendix-A.3 -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum EncryptionScheme { - /// `pbeWithMD2AndDES-CBC` - PbeWithMd2AndDesCbc, - - /// `pbeWithMD2AndRC2-CBC` - PbeWithMd2AndRc2Cbc, - - /// `pbeWithMD5AndDES-CBC` - PbeWithMd5AndDesCbc, - - /// `pbeWithMD5AndRC2-CBC` - PbeWithMd5AndRc2Cbc, - - /// `pbeWithSHA1AndDES-CBC` - PbeWithSha1AndDesCbc, - - /// `pbeWithSHA1AndRC2-CBC` - PbeWithSha1AndRc2Cbc, -} - -impl TryFrom for EncryptionScheme { - type Error = Error; - - fn try_from(oid: ObjectIdentifier) -> Result { - match oid { - PBE_WITH_MD2_AND_DES_CBC_OID => Ok(Self::PbeWithMd2AndDesCbc), - PBE_WITH_MD2_AND_RC2_CBC_OID => Ok(Self::PbeWithMd2AndRc2Cbc), - PBE_WITH_MD5_AND_DES_CBC_OID => Ok(Self::PbeWithMd5AndDesCbc), - PBE_WITH_MD5_AND_RC2_CBC_OID => Ok(Self::PbeWithMd5AndRc2Cbc), - PBE_WITH_SHA1_AND_DES_CBC_OID => Ok(Self::PbeWithSha1AndDesCbc), - PBE_WITH_SHA1_AND_RC2_CBC_OID => Ok(Self::PbeWithSha1AndRc2Cbc), - _ => Err(ErrorKind::UnknownOid { oid }.into()), - } - } -} - -impl EncryptionScheme { - /// Get the [`SymmetricCipher`] to be used. - pub fn cipher(self) -> SymmetricCipher { - match self { - Self::PbeWithMd2AndDesCbc => SymmetricCipher::DesCbc, - Self::PbeWithMd2AndRc2Cbc => SymmetricCipher::Rc2Cbc, - Self::PbeWithMd5AndDesCbc => SymmetricCipher::DesCbc, - Self::PbeWithMd5AndRc2Cbc => SymmetricCipher::Rc2Cbc, - Self::PbeWithSha1AndDesCbc => SymmetricCipher::DesCbc, - Self::PbeWithSha1AndRc2Cbc => SymmetricCipher::Rc2Cbc, - } - } - - /// Get the [`DigestAlgorithm`] to be used. - pub fn digest(self) -> DigestAlgorithm { - match self { - Self::PbeWithMd2AndDesCbc => DigestAlgorithm::Md2, - Self::PbeWithMd2AndRc2Cbc => DigestAlgorithm::Md2, - Self::PbeWithMd5AndDesCbc => DigestAlgorithm::Md5, - Self::PbeWithMd5AndRc2Cbc => DigestAlgorithm::Md5, - Self::PbeWithSha1AndDesCbc => DigestAlgorithm::Sha1, - Self::PbeWithSha1AndRc2Cbc => DigestAlgorithm::Sha1, - } - } - - /// Get the [`ObjectIdentifier`] (a.k.a OID) for this algorithm. - pub fn oid(self) -> ObjectIdentifier { - match self { - Self::PbeWithMd2AndDesCbc => PBE_WITH_MD2_AND_DES_CBC_OID, - Self::PbeWithMd2AndRc2Cbc => PBE_WITH_MD2_AND_RC2_CBC_OID, - Self::PbeWithMd5AndDesCbc => PBE_WITH_MD5_AND_DES_CBC_OID, - Self::PbeWithMd5AndRc2Cbc => PBE_WITH_MD5_AND_RC2_CBC_OID, - Self::PbeWithSha1AndDesCbc => PBE_WITH_SHA1_AND_DES_CBC_OID, - Self::PbeWithSha1AndRc2Cbc => PBE_WITH_SHA1_AND_RC2_CBC_OID, - } - } -} - -impl Encodable for EncryptionScheme { - fn encoded_len(&self) -> Result { - self.oid().encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> Result<()> { - self.oid().encode(encoder) - } -} - -/// Digest algorithms supported by PBES1. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum DigestAlgorithm { - /// MD2 - Md2, - - /// MD5 - Md5, - - /// SHA-1 - Sha1, -} - -/// Symmetric encryption ciphers supported by PBES1. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum SymmetricCipher { - /// DES in CBC mode - DesCbc, - - /// RC2 in CBC mode - Rc2Cbc, -} diff --git a/pkcs5/src/pbes2.rs b/pkcs5/src/pbes2.rs deleted file mode 100644 index 2334bf76..00000000 --- a/pkcs5/src/pbes2.rs +++ /dev/null @@ -1,303 +0,0 @@ -//! Password-Based Encryption Scheme 2 as defined in [RFC 8018 Section 6.2]. -//! -//! [RFC 8018 Section 6.2]: https://tools.ietf.org/html/rfc8018#section-6.2 - -mod kdf; - -#[cfg(feature = "pbes2")] -mod encryption; - -pub use self::kdf::{ - Kdf, Pbkdf2Params, Pbkdf2Prf, ScryptParams, HMAC_WITH_SHA1_OID, HMAC_WITH_SHA256_OID, - PBKDF2_OID, SCRYPT_OID, -}; - -use crate::{AlgorithmIdentifier, CryptoError}; -use core::convert::{TryFrom, TryInto}; -use der::{ - asn1::{Any, ObjectIdentifier, OctetString}, - Decodable, Encodable, Encoder, Error, ErrorKind, Length, Message, -}; - -#[cfg(all(feature = "alloc", feature = "pbes2"))] -use alloc::vec::Vec; - -/// 128-bit Advanced Encryption Standard (AES) algorithm with Cipher-Block -/// Chaining (CBC) mode of operation. -pub const AES_128_CBC_OID: ObjectIdentifier = ObjectIdentifier::new("2.16.840.1.101.3.4.1.2"); - -/// 256-bit Advanced Encryption Standard (AES) algorithm with Cipher-Block -/// Chaining (CBC) mode of operation. -pub const AES_256_CBC_OID: ObjectIdentifier = ObjectIdentifier::new("2.16.840.1.101.3.4.1.42"); - -/// Password-Based Encryption Scheme 2 (PBES2) OID. -/// -/// -pub const PBES2_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.5.13"); - -/// AES cipher block size -const AES_BLOCK_SIZE: usize = 16; - -/// Password-Based Encryption Scheme 2 parameters as defined in [RFC 8018 Appendix A.4]. -/// -/// ```text -/// PBES2-params ::= SEQUENCE { -/// keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}}, -/// encryptionScheme AlgorithmIdentifier {{PBES2-Encs}} } -/// ``` -/// -/// [RFC 8018 Appendix A.4]: https://tools.ietf.org/html/rfc8018#appendix-A.4 -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Parameters<'a> { - /// Key derivation function - pub kdf: Kdf<'a>, - - /// Encryption scheme - pub encryption: EncryptionScheme<'a>, -} - -impl<'a> Parameters<'a> { - /// Initialize PBES2 parameters using PBKDF2-SHA256 as the password-based - /// key derivation function and AES-128-CBC as the symmetric cipher. - pub fn pbkdf2_sha256_aes128cbc( - pbkdf2_iterations: u16, - pbkdf2_salt: &'a [u8], - aes_iv: &'a [u8; AES_BLOCK_SIZE], - ) -> Result { - let kdf = Pbkdf2Params::hmac_with_sha256(pbkdf2_iterations, pbkdf2_salt)?.into(); - let encryption = EncryptionScheme::Aes128Cbc { iv: aes_iv }; - Ok(Self { kdf, encryption }) - } - - /// Initialize PBES2 parameters using PBKDF2-SHA256 as the password-based - /// key derivation function and AES-256-CBC as the symmetric cipher. - pub fn pbkdf2_sha256_aes256cbc( - pbkdf2_iterations: u16, - pbkdf2_salt: &'a [u8], - aes_iv: &'a [u8; AES_BLOCK_SIZE], - ) -> Result { - let kdf = Pbkdf2Params::hmac_with_sha256(pbkdf2_iterations, pbkdf2_salt)?.into(); - let encryption = EncryptionScheme::Aes256Cbc { iv: aes_iv }; - Ok(Self { kdf, encryption }) - } - - /// Initialize PBES2 parameters using scrypt as the password-based - /// key derivation function and AES-128-CBC as the symmetric cipher. - /// - /// For more information on scrypt parameters, see documentation for the - /// [`scrypt::Params`] struct. - #[cfg(feature = "scrypt")] - #[cfg_attr(docsrs, doc(cfg(feature = "scrypt")))] - pub fn scrypt_aes128cbc( - params: scrypt::Params, - salt: &'a [u8], - aes_iv: &'a [u8; AES_BLOCK_SIZE], - ) -> Result { - let kdf = ScryptParams::from_params_and_salt(params, salt)?.into(); - let encryption = EncryptionScheme::Aes128Cbc { iv: aes_iv }; - Ok(Self { kdf, encryption }) - } - - /// Initialize PBES2 parameters using scrypt as the password-based - /// key derivation function and AES-256-CBC as the symmetric cipher. - /// - /// For more information on scrypt parameters, see documentation for the - /// [`scrypt::Params`] struct. - /// - /// When in doubt, use `Default::default()` as the [`scrypt::Params`]. - /// This also avoids the need to import the type from the `scrypt` crate. - #[cfg(feature = "scrypt")] - #[cfg_attr(docsrs, doc(cfg(feature = "scrypt")))] - pub fn scrypt_aes256cbc( - params: scrypt::Params, - salt: &'a [u8], - aes_iv: &'a [u8; AES_BLOCK_SIZE], - ) -> Result { - let kdf = ScryptParams::from_params_and_salt(params, salt)?.into(); - let encryption = EncryptionScheme::Aes256Cbc { iv: aes_iv }; - Ok(Self { kdf, encryption }) - } - - /// Attempt to decrypt the given ciphertext, allocating and returning a - /// byte vector containing the plaintext. - #[cfg(all(feature = "alloc", feature = "pbes2"))] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - #[cfg_attr(docsrs, doc(cfg(feature = "pbes2")))] - pub fn decrypt( - &self, - password: impl AsRef<[u8]>, - ciphertext: &[u8], - ) -> Result, CryptoError> { - let mut buffer = ciphertext.to_vec(); - let pt_len = self.decrypt_in_place(password, &mut buffer)?.len(); - buffer.truncate(pt_len); - Ok(buffer) - } - - /// Attempt to decrypt the given ciphertext in-place using a key derived - /// from the provided password and this scheme's parameters. - /// - /// Returns an error if the algorithm specified in this scheme's parameters - /// is unsupported, or if the ciphertext is malformed (e.g. not a multiple - /// of a block mode's padding) - #[cfg(feature = "pbes2")] - #[cfg_attr(docsrs, doc(cfg(feature = "pbes2")))] - pub fn decrypt_in_place<'b>( - &self, - password: impl AsRef<[u8]>, - buffer: &'b mut [u8], - ) -> Result<&'b [u8], CryptoError> { - encryption::decrypt_in_place(self, password, buffer) - } - - /// Encrypt the given plaintext, allocating and returning a vector - /// containing the ciphertext. - #[cfg(all(feature = "alloc", feature = "pbes2"))] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - #[cfg_attr(docsrs, doc(cfg(feature = "pbes2")))] - pub fn encrypt( - &self, - password: impl AsRef<[u8]>, - plaintext: &[u8], - ) -> Result, CryptoError> { - // TODO(tarcieri): support non-AES ciphers? - let mut buffer = Vec::with_capacity(plaintext.len() + AES_BLOCK_SIZE); - buffer.extend_from_slice(plaintext); - buffer.extend_from_slice(&[0u8; AES_BLOCK_SIZE]); - - let ct_len = self - .encrypt_in_place(password, &mut buffer, plaintext.len())? - .len(); - - buffer.truncate(ct_len); - Ok(buffer) - } - - /// Encrypt the given plaintext in-place using a key derived from the - /// provided password and this scheme's parameters, writing the ciphertext - /// into the same buffer. - #[cfg(feature = "pbes2")] - #[cfg_attr(docsrs, doc(cfg(feature = "pbes2")))] - pub fn encrypt_in_place<'b>( - &self, - password: impl AsRef<[u8]>, - buffer: &'b mut [u8], - pos: usize, - ) -> Result<&'b [u8], CryptoError> { - encryption::encrypt_in_place(self, password, buffer, pos) - } -} - -impl<'a> TryFrom> for Parameters<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> der::Result { - any.sequence(|params| { - let kdf = AlgorithmIdentifier::decode(params)?; - let encryption = AlgorithmIdentifier::decode(params)?; - - Ok(Self { - kdf: kdf.try_into()?, - encryption: encryption.try_into()?, - }) - }) - } -} - -impl<'a> Message<'a> for Parameters<'a> { - fn fields(&self, f: F) -> der::Result - where - F: FnOnce(&[&dyn Encodable]) -> der::Result, - { - f(&[&self.kdf, &self.encryption]) - } -} - -/// Symmetric encryption scheme used by PBES2. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub enum EncryptionScheme<'a> { - /// AES-128 in CBC mode - Aes128Cbc { - /// Initialization vector - iv: &'a [u8; AES_BLOCK_SIZE], - }, - - /// AES-256 in CBC mode - Aes256Cbc { - /// Initialization vector - iv: &'a [u8; AES_BLOCK_SIZE], - }, -} - -impl<'a> EncryptionScheme<'a> { - /// Get the size of a key used by this algorithm. - pub fn key_size(&self) -> usize { - match self { - Self::Aes128Cbc { .. } => 16, - Self::Aes256Cbc { .. } => 32, - } - } - - /// Get the [`ObjectIdentifier`] (a.k.a OID) for this algorithm. - pub fn oid(&self) -> ObjectIdentifier { - match self { - Self::Aes128Cbc { .. } => AES_128_CBC_OID, - Self::Aes256Cbc { .. } => AES_256_CBC_OID, - } - } -} - -impl<'a> TryFrom> for EncryptionScheme<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> der::Result { - AlgorithmIdentifier::try_from(any).and_then(TryInto::try_into) - } -} - -impl<'a> TryFrom> for EncryptionScheme<'a> { - type Error = Error; - - fn try_from(alg: AlgorithmIdentifier<'a>) -> der::Result { - // TODO(tarcieri): support for non-AES algorithms? - let iv = alg - .parameters_any()? - .octet_string()? - .as_bytes() - .try_into() - .map_err(|_| der::Tag::OctetString.value_error())?; - - match alg.oid { - AES_128_CBC_OID => Ok(Self::Aes128Cbc { iv }), - AES_256_CBC_OID => Ok(Self::Aes256Cbc { iv }), - oid => Err(ErrorKind::UnknownOid { oid }.into()), - } - } -} - -impl<'a> TryFrom> for AlgorithmIdentifier<'a> { - type Error = Error; - - fn try_from(scheme: EncryptionScheme<'a>) -> der::Result { - let parameters = OctetString::new(match scheme { - EncryptionScheme::Aes128Cbc { iv } => iv, - EncryptionScheme::Aes256Cbc { iv } => iv, - })?; - - Ok(AlgorithmIdentifier { - oid: scheme.oid(), - parameters: Some(parameters.into()), - }) - } -} - -impl<'a> Encodable for EncryptionScheme<'a> { - fn encoded_len(&self) -> der::Result { - AlgorithmIdentifier::try_from(*self)?.encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> der::Result<()> { - AlgorithmIdentifier::try_from(*self)?.encode(encoder) - } -} diff --git a/pkcs5/src/pbes2/encryption.rs b/pkcs5/src/pbes2/encryption.rs deleted file mode 100644 index 313638df..00000000 --- a/pkcs5/src/pbes2/encryption.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! PBES2 encryption implementation - -use super::{EncryptionScheme, Kdf, Parameters, Pbkdf2Params, Pbkdf2Prf, ScryptParams}; -use crate::CryptoError; -use block_modes::block_padding::Pkcs7; -use block_modes::{BlockMode, Cbc}; -use core::convert::TryInto; -use hmac::{ - digest::{generic_array::ArrayLength, BlockInput, FixedOutput, Reset, Update}, - Hmac, -}; -use pbkdf2::pbkdf2; -use scrypt::scrypt; -use sha2::Sha256; - -type Aes128Cbc = Cbc; -type Aes256Cbc = Cbc; - -/// Maximum size of a derived encryption key -const MAX_KEY_LEN: usize = 32; - -pub fn encrypt_in_place<'b>( - params: &Parameters<'_>, - password: impl AsRef<[u8]>, - buffer: &'b mut [u8], - pos: usize, -) -> Result<&'b [u8], CryptoError> { - let encryption_key = EncryptionKey::derive_from_password( - password.as_ref(), - ¶ms.kdf, - params.encryption.key_size(), - )?; - - match params.encryption { - EncryptionScheme::Aes128Cbc { iv } => { - let cipher = Aes128Cbc::new_from_slices(encryption_key.as_slice(), iv) - .map_err(|_| CryptoError)?; - cipher.encrypt(buffer, pos).map_err(|_| CryptoError) - } - EncryptionScheme::Aes256Cbc { iv } => { - let cipher = Aes256Cbc::new_from_slices(encryption_key.as_slice(), iv) - .map_err(|_| CryptoError)?; - cipher.encrypt(buffer, pos).map_err(|_| CryptoError) - } - } -} - -/// Decrypt a message encrypted with PBES2-based key derivation -pub fn decrypt_in_place<'a>( - params: &Parameters<'_>, - password: impl AsRef<[u8]>, - buffer: &'a mut [u8], -) -> Result<&'a [u8], CryptoError> { - let encryption_key = EncryptionKey::derive_from_password( - password.as_ref(), - ¶ms.kdf, - params.encryption.key_size(), - )?; - - match params.encryption { - EncryptionScheme::Aes128Cbc { iv } => { - let cipher = Aes128Cbc::new_from_slices(encryption_key.as_slice(), iv) - .map_err(|_| CryptoError)?; - cipher.decrypt(buffer).map_err(|_| CryptoError) - } - EncryptionScheme::Aes256Cbc { iv } => { - let cipher = Aes256Cbc::new_from_slices(encryption_key.as_slice(), iv) - .map_err(|_| CryptoError)?; - cipher.decrypt(buffer).map_err(|_| CryptoError) - } - } -} - -/// Encryption key as derived by PBKDF2 -// TODO(tarcieri): zeroize? -struct EncryptionKey { - buffer: [u8; MAX_KEY_LEN], - length: usize, -} - -impl EncryptionKey { - /// Derive an encryption key using the supplied PBKDF parameters. - pub fn derive_from_password( - password: &[u8], - kdf: &Kdf<'_>, - key_size: usize, - ) -> Result { - match kdf { - Kdf::Pbkdf2(pbkdf2_params) => { - EncryptionKey::derive_with_pbkdf2::(password, pbkdf2_params, key_size) - } - Kdf::Scrypt(scrypt_params) => { - EncryptionKey::derive_with_scrypt(password, &scrypt_params, key_size) - } - } - } - - /// Derive key using PBKDF2. - fn derive_with_pbkdf2( - password: &[u8], - params: &Pbkdf2Params<'_>, - length: usize, - ) -> Result - where - D: Update + BlockInput + FixedOutput + Reset + Default + Clone + Sync, - D::BlockSize: ArrayLength, - { - // TODO(tarcieri): move to `derive_from_password`? - validate_key_length(length, params.key_length.map(Into::into))?; - - // We only support PBKDF2-SHA256 for now - if params.prf != Pbkdf2Prf::HmacWithSha256 { - return Err(CryptoError); - } - - let mut buffer = [0u8; MAX_KEY_LEN]; - pbkdf2::>( - password, - params.salt, - params.iteration_count as u32, - &mut buffer[..length], - ); - - Ok(Self { buffer, length }) - } - - /// Derive key using scrypt. - fn derive_with_scrypt( - password: &[u8], - params: &ScryptParams<'_>, - length: usize, - ) -> Result { - // TODO(tarcieri): move to `derive_from_password`? - validate_key_length(length, params.key_length.map(Into::into))?; - - let mut buffer = [0u8; MAX_KEY_LEN]; - scrypt( - password, - params.salt, - ¶ms.try_into()?, - &mut buffer[..length], - ) - .map_err(|_| CryptoError)?; - - Ok(Self { buffer, length }) - } - - /// Get the key material as a slice - fn as_slice(&self) -> &[u8] { - &self.buffer[..self.length] - } -} - -/// Validate key length -// TODO(tarcieri): move to `EncryptionKey::derive_from_password`? -fn validate_key_length(requested_len: usize, params_len: Option) -> Result<(), CryptoError> { - // Ensure key length matches what is expected for the given algorithm - if let Some(len) = params_len { - if requested_len != len { - return Err(CryptoError); - } - } - - if requested_len > MAX_KEY_LEN { - return Err(CryptoError); - } - - Ok(()) -} diff --git a/pkcs5/src/pbes2/kdf.rs b/pkcs5/src/pbes2/kdf.rs deleted file mode 100644 index c407a12e..00000000 --- a/pkcs5/src/pbes2/kdf.rs +++ /dev/null @@ -1,417 +0,0 @@ -//! Key derivation functions. - -use crate::{AlgorithmIdentifier, CryptoError, Error}; -use core::convert::{TryFrom, TryInto}; -use der::{ - asn1::{Any, ObjectIdentifier, OctetString}, - Encodable, Encoder, ErrorKind, Length, Message, -}; - -/// Password-Based Key Derivation Function (PBKDF2) OID. -pub const PBKDF2_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.5.12"); - -/// HMAC-SHA1 (for use with PBKDF2) -pub const HMAC_WITH_SHA1_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.2.7"); - -/// HMAC-SHA-256 (for use with PBKDF2) -pub const HMAC_WITH_SHA256_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.2.9"); - -/// `id-scrypt` ([RFC 7914]) -/// -/// [RFC 7914]: https://datatracker.ietf.org/doc/html/rfc7914#section-7 -pub const SCRYPT_OID: ObjectIdentifier = ObjectIdentifier::new("1.3.6.1.4.1.11591.4.11"); - -/// Type used for expressing scrypt cost -type ScryptCost = u16; - -/// Password-based key derivation function. -#[derive(Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub enum Kdf<'a> { - /// Password-Based Key Derivation Function 2 (PBKDF2). - Pbkdf2(Pbkdf2Params<'a>), - - /// scrypt sequential memory-hard password hashing function. - Scrypt(ScryptParams<'a>), -} - -impl<'a> Kdf<'a> { - /// Get the [`ObjectIdentifier`] (a.k.a OID) for this algorithm. - pub fn oid(&self) -> ObjectIdentifier { - match self { - Self::Pbkdf2(_) => PBKDF2_OID, - Self::Scrypt(_) => SCRYPT_OID, - } - } - - /// Get [`Pbkdf2Params`] if it is the selected algorithm. - pub fn pbkdf2(&self) -> Option<&Pbkdf2Params<'a>> { - match self { - Self::Pbkdf2(params) => Some(params), - _ => None, - } - } - - /// Get [`ScryptParams`] if it is the selected algorithm. - pub fn scrypt(&self) -> Option<&ScryptParams<'a>> { - match self { - Self::Scrypt(params) => Some(params), - _ => None, - } - } - - /// Is the selected KDF PBKDF2? - pub fn is_pbkdf2(&self) -> bool { - self.pbkdf2().is_some() - } - - /// Is the selected KDF scrypt? - pub fn is_scrypt(&self) -> bool { - self.scrypt().is_some() - } -} - -impl<'a> From> for Kdf<'a> { - fn from(params: Pbkdf2Params<'a>) -> Self { - Kdf::Pbkdf2(params) - } -} - -impl<'a> From> for Kdf<'a> { - fn from(params: ScryptParams<'a>) -> Self { - Kdf::Scrypt(params) - } -} - -impl<'a> TryFrom> for Kdf<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> der::Result { - AlgorithmIdentifier::try_from(any).and_then(TryInto::try_into) - } -} - -impl<'a> TryFrom> for Kdf<'a> { - type Error = Error; - - fn try_from(alg: AlgorithmIdentifier<'a>) -> der::Result { - match alg.oid { - PBKDF2_OID => alg - .parameters_any() - .and_then(TryFrom::try_from) - .map(Self::Pbkdf2), - SCRYPT_OID => alg - .parameters_any() - .and_then(TryFrom::try_from) - .map(Self::Scrypt), - oid => Err(ErrorKind::UnknownOid { oid }.into()), - } - } -} - -impl<'a> Message<'a> for Kdf<'a> { - fn fields(&self, f: F) -> der::Result - where - F: FnOnce(&[&dyn Encodable]) -> der::Result, - { - match self { - Self::Pbkdf2(params) => f(&[&self.oid(), params]), - Self::Scrypt(params) => f(&[&self.oid(), params]), - } - } -} - -/// Password-Based Key Derivation Scheme 2 parameters as defined in -/// [RFC 8018 Appendix A.2]. -/// -/// ```text -/// PBKDF2-params ::= SEQUENCE { -/// salt CHOICE { -/// specified OCTET STRING, -/// otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}} -/// }, -/// iterationCount INTEGER (1..MAX), -/// keyLength INTEGER (1..MAX) OPTIONAL, -/// prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT -/// algid-hmacWithSHA1 } -/// ``` -/// -/// [RFC 8018 Appendix A.2]: https://tools.ietf.org/html/rfc8018#appendix-A.2 -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Pbkdf2Params<'a> { - /// PBKDF2 salt - // TODO(tarcieri): support `CHOICE` with `otherSource` - pub salt: &'a [u8], - - /// PBKDF2 iteration count - pub iteration_count: u16, - - /// PBKDF2 output length - pub key_length: Option, - - /// Pseudo-random function to use with PBKDF2 - pub prf: Pbkdf2Prf, -} - -impl<'a> Pbkdf2Params<'a> { - /// Initialize PBKDF2-SHA256 with the given iteration count and salt - pub fn hmac_with_sha256(iteration_count: u16, salt: &'a [u8]) -> Result { - Ok(Self { - salt, - iteration_count, - key_length: None, - prf: Pbkdf2Prf::HmacWithSha256, - }) - } -} - -impl<'a> TryFrom> for Pbkdf2Params<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> der::Result { - any.sequence(|params| { - // TODO(tarcieri): support salt `CHOICE` w\ `AlgorithmIdentifier` - let salt = params.octet_string()?; - let iteration_count = params.decode()?; - let key_length = params.optional()?; - let prf: Option> = params.optional()?; - - Ok(Self { - salt: salt.as_bytes(), - iteration_count, - key_length, - prf: prf.map(TryInto::try_into).transpose()?.unwrap_or_default(), - }) - }) - } -} - -impl<'a> Message<'a> for Pbkdf2Params<'a> { - fn fields(&self, f: F) -> der::Result - where - F: FnOnce(&[&dyn Encodable]) -> der::Result, - { - if self.prf == Pbkdf2Prf::default() { - f(&[ - &OctetString::new(self.salt)?, - &self.iteration_count, - &self.key_length, - ]) - } else { - f(&[ - &OctetString::new(self.salt)?, - &self.iteration_count, - &self.key_length, - &self.prf, - ]) - } - } -} - -/// Pseudo-random function used by PBKDF2. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub enum Pbkdf2Prf { - /// HMAC with SHA1 - HmacWithSha1, - - /// HMAC with SHA-256 - HmacWithSha256, -} - -impl Pbkdf2Prf { - /// Get the [`ObjectIdentifier`] (a.k.a OID) for this algorithm. - pub fn oid(self) -> ObjectIdentifier { - match self { - Self::HmacWithSha1 => HMAC_WITH_SHA1_OID, - Self::HmacWithSha256 => HMAC_WITH_SHA256_OID, - } - } -} - -/// Default PRF as specified in RFC 8018 Appendix A.2: -/// -/// ```text -/// prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1 -/// ``` -/// -/// Note that modern usage should avoid the use of SHA-1, despite it being -/// the "default" here. -impl Default for Pbkdf2Prf { - fn default() -> Self { - Self::HmacWithSha1 - } -} - -impl<'a> TryFrom> for Pbkdf2Prf { - type Error = Error; - - fn try_from(any: Any<'a>) -> der::Result { - AlgorithmIdentifier::try_from(any).and_then(TryInto::try_into) - } -} - -impl<'a> TryFrom> for Pbkdf2Prf { - type Error = Error; - - fn try_from(alg: AlgorithmIdentifier<'a>) -> der::Result { - if let Some(params) = alg.parameters { - // TODO(tarcieri): support non-NULL parameters? - if !params.is_null() { - return Err(params.tag().value_error()); - } - } else { - // TODO(tarcieri): support OPTIONAL parameters? - return Err(ErrorKind::Truncated.into()); - } - - match alg.oid { - HMAC_WITH_SHA1_OID => Ok(Self::HmacWithSha1), - HMAC_WITH_SHA256_OID => Ok(Self::HmacWithSha256), - oid => Err(ErrorKind::UnknownOid { oid }.into()), - } - } -} - -impl<'a> From for AlgorithmIdentifier<'a> { - fn from(prf: Pbkdf2Prf) -> Self { - // TODO(tarcieri): support non-NULL parameters? - let parameters = der::asn1::Null; - - AlgorithmIdentifier { - oid: prf.oid(), - parameters: Some(parameters.into()), - } - } -} - -impl Encodable for Pbkdf2Prf { - fn encoded_len(&self) -> der::Result { - AlgorithmIdentifier::try_from(*self)?.encoded_len() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> der::Result<()> { - AlgorithmIdentifier::try_from(*self)?.encode(encoder) - } -} - -/// scrypt parameters as defined in [RFC 7914 Section 7.1]. -/// -/// ```text -/// scrypt-params ::= SEQUENCE { -/// salt OCTET STRING, -/// costParameter INTEGER (1..MAX), -/// blockSize INTEGER (1..MAX), -/// parallelizationParameter INTEGER (1..MAX), -/// keyLength INTEGER (1..MAX) OPTIONAL -/// } -/// ``` -/// -/// [RFC 7914 Section 7.1]: https://datatracker.ietf.org/doc/html/rfc7914#section-7.1 -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct ScryptParams<'a> { - /// scrypt salt - pub salt: &'a [u8], - - /// CPU/Memory cost parameter `N`. - pub cost_parameter: ScryptCost, - - /// Block size parameter `r`. - pub block_size: u16, - - /// Parallelization parameter `p`. - pub parallelization: u16, - - /// PBKDF2 output length - pub key_length: Option, -} - -impl<'a> ScryptParams<'a> { - /// Get the [`ScryptParams`] for the provided upstream [`scrypt::Params`] - /// and a provided salt string. - #[cfg(feature = "scrypt")] - #[cfg_attr(docsrs, doc(cfg(feature = "scrypt")))] - pub fn from_params_and_salt( - params: scrypt::Params, - salt: &'a [u8], - ) -> Result { - Ok(Self { - salt, - cost_parameter: 1 << params.log_n(), - block_size: params.r().try_into().map_err(|_| CryptoError)?, - parallelization: params.p().try_into().map_err(|_| CryptoError)?, - key_length: None, - }) - } -} - -impl<'a> TryFrom> for ScryptParams<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> der::Result { - any.sequence(|params| { - let salt = params.octet_string()?; - let cost_parameter = params.decode()?; - let block_size = params.decode()?; - let parallelization = params.decode()?; - let key_length = params.optional()?; - - Ok(Self { - salt: salt.as_bytes(), - cost_parameter, - block_size, - parallelization, - key_length, - }) - }) - } -} - -impl<'a> Message<'a> for ScryptParams<'a> { - fn fields(&self, f: F) -> der::Result - where - F: FnOnce(&[&dyn Encodable]) -> der::Result, - { - f(&[ - &OctetString::new(self.salt)?, - &self.cost_parameter, - &self.block_size, - &self.parallelization, - &self.key_length, - ]) - } -} - -#[cfg(feature = "scrypt")] -#[cfg_attr(docsrs, doc(cfg(feature = "scrypt")))] -impl<'a> TryFrom> for scrypt::Params { - type Error = CryptoError; - - fn try_from(params: ScryptParams<'a>) -> Result { - scrypt::Params::try_from(¶ms) - } -} - -#[cfg(feature = "scrypt")] -#[cfg_attr(docsrs, doc(cfg(feature = "scrypt")))] -impl<'a> TryFrom<&ScryptParams<'a>> for scrypt::Params { - type Error = CryptoError; - - fn try_from(params: &ScryptParams<'a>) -> Result { - let n = params.cost_parameter; - - // Compute log2 and verify its correctness - let log_n = ((8 * core::mem::size_of::() as u32) - n.leading_zeros() - 1) as u8; - - if 1 << log_n != n { - return Err(CryptoError); - } - - scrypt::Params::new( - log_n, - params.block_size.into(), - params.parallelization.into(), - ) - .map_err(|_| CryptoError) - } -} diff --git a/pkcs5/tests/encryption.rs b/pkcs5/tests/encryption.rs deleted file mode 100644 index a84bc49f..00000000 --- a/pkcs5/tests/encryption.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! PBES2 encryption tests - -#![cfg(feature = "pbes2")] - -use core::convert::TryFrom; -use hex_literal::hex; - -/// PBES2 + PBKDF2-SHA256 + AES-256-CBC `AlgorithmIdentifier` example. -/// -/// Generated by OpenSSL and extracted from the `pkcs8` crate's -/// `tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der` test vector. -const PBES2_PBKDF2_SHA256_AES256CBC_ALG_ID: &[u8] = &hex!( - "305706092a864886f70d01050d304a302906092a864886f70d01050c301c0408 - 79d982e70df91a8802020800300c06082a864886f70d02090500301d06096086 - 4801650304012a0410b2d02d78b2efd9dff694cf8e0af40925" -); - -/// PBES2 + scrypt + AES-256-CBC `AlgorithmIdentifier` example. -/// -/// Generated by OpenSSL and extracted from the `pkcs8` crate's -/// `ed25519-encpriv-aes256-scrypt.der` test vector. -const PBES2_SCRYPT_AES256CBC_ALG_ID: &[u8] = &hex!( - "304f06092a864886f70d01050d3042302106092b06010401da47040b30140408 - e6211e2348ad69e002024000020108020101301d060960864801650304012a041 - 09bd0a6251f2254f9fd5963887c27cf01" -); - -/// Plaintext of Ed25519 PKCS#8 private key. -/// -/// This is the hex-encoded contents of `ed25519-priv.der` from -/// `pkcs8/tests/examples`. -const ED25519_PKCS8_KEY_PLAINTEXT: &[u8] = &hex!( - "302e020100300506032b65700422042017ed9c73e9db649ec189a612831c5fc5 - 70238207c1aa9dfbd2c53e3ff5e5ea85" -); - -/// Ciphertext of Ed25519 PKCS#8 private key when encrypted using -/// PBKDF2-SHA256 as the KDF. -/// -/// Extracted with: -/// $ openssl asn1parse -inform der -in pkcs8/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der -const ED25519_PKCS8_KEY_CIPHERTEXT_PBKDF2_SHA256: &[u8] = &hex!( - "D0CD6C770F4BB87176422305C17401809E226674CE74185D221BFDAA95069890 - C8882FCE02B05D41BCBF54B035595BCD4154B32593708469B86AACF8815A7B2B" -); - -/// Ciphertext of Ed25519 PKCS#8 private key when encrypted using -/// scrypt as the KDF. -/// -/// Extracted with: -/// $ openssl asn1parse -inform der -in pkcs8/tests/examples/ed25519-encpriv-aes256-scrypt.der -const ED25519_PKCS8_KEY_CIPHERTEXT_SCRYPT: &[u8] = &hex!( - "CC62BA773C0F495FAB3668E4FCEFCDB08E78A0EE15E0A15013F62ABE08DAA742 - 065EEB366D6E6C98CC3B0E7E69BDC861C88AFEB8F03DBA1E2C6D99D06D17360C" -); - -/// Password used to encrypt the keys. -const PASSWORD: &[u8] = b"hunter42"; // Bad password; don't actually use outside tests! - -#[test] -fn decrypt_pbes2_pbkdf2_sha256_aes256cbc() { - let scheme = pkcs5::EncryptionScheme::try_from(PBES2_PBKDF2_SHA256_AES256CBC_ALG_ID).unwrap(); - let mut buffer = Vec::from(ED25519_PKCS8_KEY_CIPHERTEXT_PBKDF2_SHA256); - let plaintext = scheme.decrypt_in_place(PASSWORD, &mut buffer).unwrap(); - assert_eq!(plaintext, ED25519_PKCS8_KEY_PLAINTEXT); -} - -#[test] -fn decrypt_pbes2_scrypt_aes256cbc() { - let scheme = pkcs5::EncryptionScheme::try_from(PBES2_SCRYPT_AES256CBC_ALG_ID).unwrap(); - let mut buffer = Vec::from(ED25519_PKCS8_KEY_CIPHERTEXT_SCRYPT); - let plaintext = scheme.decrypt_in_place(PASSWORD, &mut buffer).unwrap(); - assert_eq!(plaintext, ED25519_PKCS8_KEY_PLAINTEXT); -} diff --git a/pkcs5/tests/pbes2.rs b/pkcs5/tests/pbes2.rs deleted file mode 100644 index 8b1d28c7..00000000 --- a/pkcs5/tests/pbes2.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Password-Based Encryption Scheme 2 tests - -use core::convert::TryFrom; -use der::Encodable; -use hex_literal::hex; -use pkcs5::pbes2; - -/// PBES2 + PBKDF2-SHA1 + AES-128-CBC `AlgorithmIdentifier` example. -/// -/// Generated by OpenSSL and extracted from the `pkcs8` crate's -/// `tests/examples/ed25519-encpriv-aes128-pbkdf2-sha1.der` test vector. -const PBES2_PBKDF2_SHA1_AES128CBC_ALG_ID: &[u8] = &hex!( - "304906092a864886f70d01050d303c301b06092a864886f70d01050c300e0408 - e8765e01e43b6bad02020800301d06096086480165030401020410223080a71b - cd2b9a256d876c924979d2" -); - -/// PBES2 + PBKDF2-SHA256 + AES-256-CBC `AlgorithmIdentifier` example. -/// -/// Generated by OpenSSL and extracted from the `pkcs8` crate's -/// `tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der` test vector. -const PBES2_PBKDF2_SHA256_AES256CBC_ALG_ID: &[u8] = &hex!( - "305706092a864886f70d01050d304a302906092a864886f70d01050c301c0408 - 79d982e70df91a8802020800300c06082a864886f70d02090500301d06096086 - 4801650304012a0410b2d02d78b2efd9dff694cf8e0af40925" -); - -/// PBES2 + scrypt + AES-256-CBC `AlgorithmIdentifier` example. -/// -/// Generated by OpenSSL and extracted from the `pkcs8` crate's -/// `ed25519-encpriv-aes256-scrypt.der` test vector. -const PBES2_SCRYPT_AES256CBC_ALG_ID: &[u8] = &hex!( - "304f06092a864886f70d01050d3042302106092b06010401da47040b30140408 - e6211e2348ad69e002024000020108020101301d060960864801650304012a041 - 09bd0a6251f2254f9fd5963887c27cf01" -); - -/// Decoding test for PBES2 + PBKDF2-SHA1 + AES-128-CBC `AlgorithmIdentifier` -#[test] -fn decode_pbes2_pbkdf2_sha1_aes128cbc() { - let scheme = pkcs5::EncryptionScheme::try_from(PBES2_PBKDF2_SHA1_AES128CBC_ALG_ID).unwrap(); - let params = scheme.pbes2().unwrap(); - - let pbkdf2_params = params.kdf.pbkdf2().unwrap(); - assert_eq!(pbkdf2_params.salt, &hex!("e8765e01e43b6bad")); - assert_eq!(pbkdf2_params.iteration_count, 2048); - assert_eq!(pbkdf2_params.key_length, None); - assert_eq!(pbkdf2_params.prf, pbes2::Pbkdf2Prf::HmacWithSha1); - - match params.encryption { - pbes2::EncryptionScheme::Aes128Cbc { iv } => { - assert_eq!(iv, &hex!("223080a71bcd2b9a256d876c924979d2")); - } - other => panic!("unexpected encryption scheme: {:?}", other), - } -} - -/// Decoding test for PBES2 + PBKDF2-SHA256 + AES-256-CBC `AlgorithmIdentifier` -#[test] -fn decode_pbes2_pbkdf2_sha256_aes256cbc() { - let scheme = pkcs5::EncryptionScheme::try_from(PBES2_PBKDF2_SHA256_AES256CBC_ALG_ID).unwrap(); - let params = scheme.pbes2().unwrap(); - - let pbkdf2_params = params.kdf.pbkdf2().unwrap(); - assert_eq!(pbkdf2_params.salt, &hex!("79d982e70df91a88")); - assert_eq!(pbkdf2_params.iteration_count, 2048); - assert_eq!(pbkdf2_params.key_length, None); - assert_eq!(pbkdf2_params.prf, pbes2::Pbkdf2Prf::HmacWithSha256); - - match params.encryption { - pbes2::EncryptionScheme::Aes256Cbc { iv } => { - assert_eq!(iv, &hex!("b2d02d78b2efd9dff694cf8e0af40925")); - } - other => panic!("unexpected encryption scheme: {:?}", other), - } -} - -/// Decoding test for PBES2 + scrypt + AES-256-CBC `AlgorithmIdentifier` -#[test] -fn decode_pbes2_scrypt_aes256cbc() { - let scheme = pkcs5::EncryptionScheme::try_from(PBES2_SCRYPT_AES256CBC_ALG_ID).unwrap(); - let params = scheme.pbes2().unwrap(); - - let scrypt_params = params.kdf.scrypt().unwrap(); - assert_eq!(scrypt_params.salt, &hex!("E6211E2348AD69E0")); - assert_eq!(scrypt_params.cost_parameter, 16384); - assert_eq!(scrypt_params.block_size, 8); - assert_eq!(scrypt_params.parallelization, 1); - assert_eq!(scrypt_params.key_length, None); - - match params.encryption { - pbes2::EncryptionScheme::Aes256Cbc { iv } => { - assert_eq!(iv, &hex!("9BD0A6251F2254F9FD5963887C27CF01")); - } - other => panic!("unexpected encryption scheme: {:?}", other), - } -} - -/// Encoding test for PBES2 + PBKDF2-SHA1 + AES-128-CBC `AlgorithmIdentifier` -#[test] -fn encode_pbes2_pbkdf2_sha1_aes128cbc() { - let mut buffer = [0u8; 1024]; - - let scheme = pkcs5::EncryptionScheme::try_from(PBES2_PBKDF2_SHA1_AES128CBC_ALG_ID).unwrap(); - let mut encoder = der::Encoder::new(&mut buffer); - scheme.encode(&mut encoder).unwrap(); - - let encoded_der = encoder.finish().unwrap(); - assert_eq!(encoded_der, PBES2_PBKDF2_SHA1_AES128CBC_ALG_ID); -} - -/// Encoding test for PBES2 + PBKDF2-SHA256 + AES-256-CBC `AlgorithmIdentifier` -#[test] -fn encode_pbes2_pbkdf2_sha256_aes256cbc() { - let mut buffer = [0u8; 1024]; - - let scheme = pkcs5::EncryptionScheme::try_from(PBES2_PBKDF2_SHA256_AES256CBC_ALG_ID).unwrap(); - let mut encoder = der::Encoder::new(&mut buffer); - scheme.encode(&mut encoder).unwrap(); - - let encoded_der = encoder.finish().unwrap(); - assert_eq!(encoded_der, PBES2_PBKDF2_SHA256_AES256CBC_ALG_ID); -} - -/// Encoding test for PBES2 + scrypt + AES-256-CBC `AlgorithmIdentifier` -#[test] -fn encode_pbes2_scrypt_aes256cbc() { - let mut buffer = [0u8; 1024]; - - let scheme = pkcs5::EncryptionScheme::try_from(PBES2_SCRYPT_AES256CBC_ALG_ID).unwrap(); - let mut encoder = der::Encoder::new(&mut buffer); - scheme.encode(&mut encoder).unwrap(); - - let encoded_der = encoder.finish().unwrap(); - assert_eq!(encoded_der, PBES2_SCRYPT_AES256CBC_ALG_ID); -} diff --git a/pkcs8/CHANGELOG.md b/pkcs8/CHANGELOG.md deleted file mode 100644 index d820a0ef..00000000 --- a/pkcs8/CHANGELOG.md +++ /dev/null @@ -1,213 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.7.5 (2021-07-26) -### Added -- Support for customizing PEM `LineEnding` ([#554]) - -### Changed -- Bump `pem-rfc7468` dependency to v0.2 ([#552]) - -[#552]: https://github.com/RustCrypto/utils/pull/552 -[#554]: https://github.com/RustCrypto/utils/pull/554 - -## 0.7.4 (2021-07-25) -### Added -- PKCS#1 support ([#543]) - -[#543]: https://github.com/RustCrypto/utils/pull/543 - -## 0.7.3 (2021-07-24) -### Changed -- Use `pem-rfc7468` crate ([#530]) - -[#530]: https://github.com/RustCrypto/utils/pull/530 - -## 0.7.2 (2021-07-20) -### Added -- `Error::ParametersMalformed` variant ([#523]) - -[#523]: https://github.com/RustCrypto/utils/pull/523 - -## 0.7.1 (2021-07-20) -### Added -- `Error::KeyMalformed` variant ([#521]) - -[#521]: https://github.com/RustCrypto/utils/pull/521 - -## 0.7.0 (2021-06-07) -### Added -- ASN.1 error improvements ([#478]) - -### Changed -- Merge `OneAsymmetricKey` into `PrivateKeyInfo` ([#467]) -- Use scrypt as the default PBES2 KDF ([#468]) -- Return `Result`(s) when encoding ([#478]) -- Bump `der` to v0.4 ([#490]) -- Bump `spki` to v0.4 ([#491]) -- Bump `pkcs5` to v0.3 ([#492]) - -[#467]: https://github.com/RustCrypto/utils/pull/467 -[#468]: https://github.com/RustCrypto/utils/pull/468 -[#478]: https://github.com/RustCrypto/utils/pull/478 -[#490]: https://github.com/RustCrypto/utils/pull/490 -[#491]: https://github.com/RustCrypto/utils/pull/491 -[#492]: https://github.com/RustCrypto/utils/pull/492 - -## 0.6.1 (2021-05-24) -### Added -- Support for RFC5958's `OneAsymmetricKey` ([#424], [#425]) - -### Changed -- Bump `der` to v0.3.5 ([#430]) - -[#424]: https://github.com/RustCrypto/utils/pull/424 -[#425]: https://github.com/RustCrypto/utils/pull/425 -[#430]: https://github.com/RustCrypto/utils/pull/430 - -## 0.6.0 (2021-03-22) -### Changed -- Bump `der` dependency to v0.3 ([#354]) -- Bump `spki` dependency to v0.3 ([#355]) -- Bump `pkcs5` dependency to v0.2 ([#356]) - -[#354]: https://github.com/RustCrypto/utils/pull/354 -[#355]: https://github.com/RustCrypto/utils/pull/355 -[#356]: https://github.com/RustCrypto/utils/pull/356 - -## 0.5.5 (2021-03-17) -### Changed -- Bump `base64ct` dependency to v1.0 ([#335]) - -[#335]: https://github.com/RustCrypto/utils/pull/335 - -## 0.5.4 (2021-02-24) -### Added -- Encryption helper methods for `FromPrivateKey`/`ToPrivateKey` ([#308]) - -[#308]: https://github.com/RustCrypto/utils/pull/308 - -## 0.5.3 (2021-02-23) -### Added -- Support for decrypting/encrypting `EncryptedPrivateKeyInfo` ([#293], [#302]) -- PEM support for `EncryptedPrivateKeyInfo` ([#301]) -- `Error::Crypto` variant ([#305]) - -[#293]: https://github.com/RustCrypto/utils/pull/293 -[#301]: https://github.com/RustCrypto/utils/pull/301 -[#302]: https://github.com/RustCrypto/utils/pull/302 -[#305]: https://github.com/RustCrypto/utils/pull/305 - -## 0.5.2 (2021-02-20) -### Changed -- Use `pkcs5` crate ([#290]) - -[#290]: https://github.com/RustCrypto/utils/pull/290 - -## 0.5.1 (2021-02-18) [YANKED] -### Added -- `pkcs5` feature ([#278]) - -### Changed -- Bump `spki` dependency to v0.2.0 ([#277]) - -[#277]: https://github.com/RustCrypto/utils/pull/277 -[#278]: https://github.com/RustCrypto/utils/pull/278 - -## 0.5.0 (2021-02-16) [YANKED] -### Added -- Initial `EncryptedPrivateKeyInfo` support ([#262]) - -### Changed -- Extract SPKI-related types into the `spki` crate ([#261], [#268]) - -[#261]: https://github.com/RustCrypto/utils/pull/261 -[#262]: https://github.com/RustCrypto/utils/pull/262 -[#268]: https://github.com/RustCrypto/utils/pull/268 - -## 0.4.1 (2021-02-01) -### Changed -- Bump `basec4ct` dependency to v0.2 ([#238], [#243]) - -[#238]: https://github.com/RustCrypto/utils/pull/238 -[#243]: https://github.com/RustCrypto/utils/pull/243 - -## 0.4.0 (2021-01-26) -### Changed -- Bump `der` crate dependency to v0.2 ([#224]) -- Use `base64ct` v0.1 for PEM encoding ([#232]) - -[#224]: https://github.com/RustCrypto/utils/pull/224 -[#232]: https://github.com/RustCrypto/utils/pull/232 - -## 0.3.3 (2020-12-21) -### Changed -- Use `der` crate for decoding/encoding ASN.1 DER ([#153], [#180]) - -[#153]: https://github.com/RustCrypto/utils/pull/153 -[#180]: https://github.com/RustCrypto/utils/pull/180 - -## 0.3.2 (2020-12-16) -### Added -- `AlgorithmIdentifier::parameters_oid` method ([#148]) - -[#148]: https://github.com/RustCrypto/utils/pull/148 - -## 0.3.1 (2020-12-16) -### Changed -- Bump `const-oid` dependency to v0.4 ([#145]) - -[#145]: https://github.com/RustCrypto/utils/pull/145 - -## 0.3.0 (2020-12-16) [YANKED] -### Added -- `AlgorithmParameters` enum ([#138]) - -[#138]: https://github.com/RustCrypto/utils/pull/138 - -## 0.2.2 (2020-12-14) -### Fixed -- Decoding/encoding support for Ed25519 keys ([#134], [#135]) - -[#134]: https://github.com/RustCrypto/utils/pull/134 -[#135]: https://github.com/RustCrypto/utils/pull/135 - -## 0.2.1 (2020-12-14) -### Added -- rustdoc improvements ([#130]) - -[#130]: https://github.com/RustCrypto/utils/pull/130 - -## 0.2.0 (2020-12-14) -### Added -- File writing methods for public/private keys ([#126]) -- Methods for loading `*Document` types from files ([#125]) -- DER encoding support ([#120], [#121]) -- PEM encoding support ([#122], [#124]) -- `ToPrivateKey`/`ToPublicKey` traits ([#123]) - -### Changed -- `Error` enum ([#128]) -- Rename `load_*_file` methods to `read_*_file` ([#127]) - -[#128]: https://github.com/RustCrypto/utils/pull/128 -[#127]: https://github.com/RustCrypto/utils/pull/127 -[#126]: https://github.com/RustCrypto/utils/pull/126 -[#125]: https://github.com/RustCrypto/utils/pull/125 -[#124]: https://github.com/RustCrypto/utils/pull/124 -[#123]: https://github.com/RustCrypto/utils/pull/123 -[#122]: https://github.com/RustCrypto/utils/pull/122 -[#121]: https://github.com/RustCrypto/utils/pull/121 -[#120]: https://github.com/RustCrypto/utils/pull/120 - -## 0.1.1 (2020-12-06) -### Added -- Helper methods to load keys from the local filesystem ([#115]) - -[#115]: https://github.com/RustCrypto/utils/pull/115 - -## 0.1.0 (2020-12-05) -- Initial release diff --git a/pkcs8/Cargo.toml b/pkcs8/Cargo.toml deleted file mode 100644 index 3a18e150..00000000 --- a/pkcs8/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "pkcs8" -version = "0.7.5" # Also update html_root_url in lib.rs when bumping this -description = """ -Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8: -Private-Key Information Syntax Specification (RFC 5208), with additional -support for PKCS#8v2 asymmetric key packages (RFC 5958) -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -repository = "https://github.com/RustCrypto/utils/tree/master/pkcs8" -categories = ["cryptography", "data-structures", "encoding", "no-std", "parser-implementations"] -keywords = ["crypto", "key", "pkcs", "private"] -readme = "README.md" - -[dependencies] -der = { version = "0.4", features = ["oid"], path = "../der" } -spki = { version = "0.4", path = "../spki" } - -# optional dependencies -rand_core = { version = "0.6", optional = true, default-features = false } -pkcs1 = { version = "0.2", optional = true, features = ["alloc"], path = "../pkcs1" } -pkcs5 = { version = "0.3", optional = true, path = "../pkcs5" } -pem-rfc7468 = { version = "0.2", optional = true, path = "../pem-rfc7468" } -zeroize = { version = "1", optional = true, default-features = false, features = ["alloc"] } - -[dev-dependencies] -hex-literal = "0.3" - -[features] -encryption = ["alloc", "pkcs5/alloc", "pkcs5/pbes2", "rand_core"] -std = ["alloc", "der/std"] -alloc = ["der/alloc", "zeroize"] -pem = ["alloc", "pem-rfc7468/alloc"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/pkcs8/LICENSE-APACHE b/pkcs8/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/pkcs8/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/pkcs8/LICENSE-MIT b/pkcs8/LICENSE-MIT deleted file mode 100644 index 2726e14a..00000000 --- a/pkcs8/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2020 The RustCrypto Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/pkcs8/README.md b/pkcs8/README.md deleted file mode 100644 index 8d7a37e5..00000000 --- a/pkcs8/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# RustCrypto: PKCS#8 (Private Keys) - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8: -Private-Key Information Syntax Specification ([RFC 5208]). - -[Documentation][docs-link] - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/pkcs8.svg -[crate-link]: https://crates.io/crates/pkcs8 -[docs-image]: https://docs.rs/pkcs8/badge.svg -[docs-link]: https://docs.rs/pkcs8/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/pkcs8/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions - -[//]: # (general links) - -[RFC 5208]: https://tools.ietf.org/html/rfc5208 diff --git a/pkcs8/src/attributes.rs b/pkcs8/src/attributes.rs deleted file mode 100644 index 725a2737..00000000 --- a/pkcs8/src/attributes.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! PKCS#8 attributes. - -use core::convert::TryFrom; -use der::{asn1::Any, Encodable, Encoder, Length}; - -/// Attributes as defined in [RFC 5958 Section 2]. -/// -/// > attributes is OPTIONAL. It contains information corresponding to -/// > the public key (e.g., certificates). The attributes field uses the -/// > class `ATTRIBUTE` which is restricted by the -/// > `OneAsymmetricKeyAttributes` information object set. -/// > `OneAsymmetricKeyAttributes` is an open ended set in this document. -/// > Others documents can constrain these values. Attributes from -/// > RFC2985 MAY be supported. -/// -/// Attributes have the following ASN.1 schema: -/// -/// ```text -/// Attributes ::= SET OF Attribute { { OneAsymmetricKeyAttributes } } -/// ``` -/// -/// [RFC 5958 Section 2]: https://datatracker.ietf.org/doc/html/rfc5958 -// TODO(tarcieri): support parsing attributes as a `der::SetOf`? -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct Attributes<'a>(Any<'a>); - -impl<'a> From> for Any<'a> { - fn from(attrs: Attributes<'a>) -> Any<'a> { - attrs.0 - } -} - -impl<'a> TryFrom> for Attributes<'a> { - type Error = der::Error; - - fn try_from(any: Any<'a>) -> der::Result> { - Ok(Attributes(any)) - } -} - -impl<'a> Encodable for Attributes<'a> { - /// Compute the length of this value in bytes when encoded as ASN.1 DER. - fn encoded_len(&self) -> der::Result { - self.0.encoded_len() - } - - /// Encode this value as ASN.1 DER using the provided [`Encoder`]. - fn encode(&self, encoder: &mut Encoder<'_>) -> der::Result<()> { - self.0.encode(encoder) - } -} diff --git a/pkcs8/src/document.rs b/pkcs8/src/document.rs deleted file mode 100644 index cb2508d9..00000000 --- a/pkcs8/src/document.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Serialized DER-encoded documents stored in heap-backed buffers. -// TODO(tarcieri): heapless support? - -#[cfg(feature = "pkcs5")] -pub(crate) mod encrypted_private_key; -pub(crate) mod private_key; -pub(crate) mod public_key; diff --git a/pkcs8/src/document/encrypted_private_key.rs b/pkcs8/src/document/encrypted_private_key.rs deleted file mode 100644 index 4725268a..00000000 --- a/pkcs8/src/document/encrypted_private_key.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! PKCS#8 encrypted private key document. - -use crate::{error, EncryptedPrivateKeyInfo, Error, Result}; -use alloc::{borrow::ToOwned, vec::Vec}; -use core::{ - convert::{TryFrom, TryInto}, - fmt, -}; -use der::Encodable; -use zeroize::{Zeroize, Zeroizing}; - -#[cfg(feature = "encryption")] -use crate::PrivateKeyDocument; - -#[cfg(feature = "pem")] -use { - crate::{encrypted_private_key_info::PEM_TYPE_LABEL, pem, LineEnding}, - alloc::string::String, - core::str::FromStr, -}; - -#[cfg(feature = "std")] -use { - super::private_key::write_secret_file, - std::{fs, path::Path}, -}; - -/// Encrypted PKCS#8 private key document. -/// -/// This type provides heap-backed storage for [`EncryptedPrivateKeyInfo`] -/// encoded as ASN.1 DER with the invariant that the contained-document is -/// "well-formed", i.e. it will parse successfully according to this crate's -/// parsing rules. -#[derive(Clone, Eq, PartialEq)] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs5")))] -pub struct EncryptedPrivateKeyDocument(Zeroizing>); - -impl EncryptedPrivateKeyDocument { - /// Attempt to decrypt this encrypted private key using the provided - /// password to derive an encryption key. - #[cfg(feature = "encryption")] - #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))] - pub fn decrypt(&self, password: impl AsRef<[u8]>) -> Result { - self.encrypted_private_key_info().decrypt(password) - } - - /// Parse the [`EncryptedPrivateKeyInfo`] contained in this [`EncryptedPrivateKeyDocument`]. - pub fn encrypted_private_key_info(&self) -> EncryptedPrivateKeyInfo<'_> { - EncryptedPrivateKeyInfo::try_from(self.0.as_ref()) - .expect("malformed EncryptedPrivateKeyDocument") - } - - /// Parse [`EncryptedPrivateKeyDocument`] from ASN.1 DER-encoded PKCS#8. - pub fn from_der(bytes: &[u8]) -> Result { - bytes.try_into() - } - - /// Parse [`EncryptedPrivateKeyDocument`] from PEM-encoded PKCS#8. - /// - /// PEM-encoded encrypted private keys can be identified by the leading - /// delimiter: - /// - /// ```text - /// -----BEGIN ENCRYPTED PRIVATE KEY----- - /// ``` - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn from_pem(s: &str) -> Result { - let (label, der_bytes) = pem::decode_vec(s.as_bytes())?; - - if label != PEM_TYPE_LABEL { - return Err(pem::Error::Label.into()); - } - - Self::from_der(&*der_bytes) - } - - /// Serialize [`EncryptedPrivateKeyDocument`] as self-zeroizing PEM-encoded - /// PKCS#8 string. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem(&self) -> Zeroizing { - self.to_pem_with_le(LineEnding::default()) - } - - /// Serialize [`EncryptedPrivateKeyDocument`] as self-zeroizing PEM-encoded - /// PKCS#8 string with the given [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem_with_le(&self, line_ending: LineEnding) -> Zeroizing { - Zeroizing::new( - pem::encode_string(PEM_TYPE_LABEL, line_ending, &self.0) - .expect(error::PEM_ENCODING_MSG), - ) - } - - /// Load [`EncryptedPrivateKeyDocument`] from an ASN.1 DER-encoded file on - /// the local filesystem (binary format). - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn read_der_file(path: impl AsRef) -> Result { - fs::read(path)?.try_into() - } - - /// Load [`EncryptedPrivateKeyDocument`] from a PEM-encoded file on the - /// local filesystem. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn read_pem_file(path: impl AsRef) -> Result { - Self::from_pem(&Zeroizing::new(fs::read_to_string(path)?)) - } - - /// Write ASN.1 DER-encoded PKCS#8 encrypted private key to the given path. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn write_der_file(&self, path: impl AsRef) -> Result<()> { - write_secret_file(path, self.as_ref()) - } - - /// Write PEM-encoded PKCS#8 encrypted private key to the given path. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn write_pem_file(&self, path: impl AsRef) -> Result<()> { - write_secret_file(path, self.to_pem().as_bytes()) - } -} - -impl AsRef<[u8]> for EncryptedPrivateKeyDocument { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl From> for EncryptedPrivateKeyDocument { - fn from(key: EncryptedPrivateKeyInfo<'_>) -> EncryptedPrivateKeyDocument { - EncryptedPrivateKeyDocument::from(&key) - } -} - -impl From<&EncryptedPrivateKeyInfo<'_>> for EncryptedPrivateKeyDocument { - fn from(key: &EncryptedPrivateKeyInfo<'_>) -> EncryptedPrivateKeyDocument { - key.to_vec() - .ok() - .and_then(|buf| buf.try_into().ok()) - .expect(error::DER_ENCODING_MSG) - } -} - -impl TryFrom<&[u8]> for EncryptedPrivateKeyDocument { - type Error = Error; - - fn try_from(bytes: &[u8]) -> Result { - // Ensure document is well-formed - EncryptedPrivateKeyInfo::try_from(bytes)?; - Ok(Self(Zeroizing::new(bytes.to_owned()))) - } -} - -impl TryFrom> for EncryptedPrivateKeyDocument { - type Error = Error; - - fn try_from(mut bytes: Vec) -> Result { - // Ensure document is well-formed - if let Err(err) = EncryptedPrivateKeyInfo::try_from(bytes.as_slice()) { - bytes.zeroize(); - return Err(err); - } - - Ok(Self(Zeroizing::new(bytes))) - } -} - -impl fmt::Debug for EncryptedPrivateKeyDocument { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_tuple("EncryptedPrivateKeyDocument") - .field(&self.encrypted_private_key_info()) - .finish() - } -} - -#[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -impl FromStr for EncryptedPrivateKeyDocument { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::from_pem(s) - } -} diff --git a/pkcs8/src/document/private_key.rs b/pkcs8/src/document/private_key.rs deleted file mode 100644 index 3a3ba394..00000000 --- a/pkcs8/src/document/private_key.rs +++ /dev/null @@ -1,258 +0,0 @@ -//! PKCS#8 private key document. - -use crate::{error, Error, PrivateKeyInfo, Result}; -use alloc::{borrow::ToOwned, vec::Vec}; -use core::{ - convert::{TryFrom, TryInto}, - fmt, -}; -use der::Encodable; -use zeroize::{Zeroize, Zeroizing}; - -#[cfg(feature = "encryption")] -use { - crate::{EncryptedPrivateKeyDocument, EncryptedPrivateKeyInfo}, - pkcs5::pbes2, - rand_core::{CryptoRng, RngCore}, -}; - -#[cfg(feature = "pem")] -use { - crate::{pem, private_key_info::PEM_TYPE_LABEL, LineEnding}, - alloc::string::String, - core::str::FromStr, -}; - -#[cfg(feature = "std")] -use std::{fs, path::Path, str}; - -/// PKCS#8 private key document. -/// -/// This type provides storage for [`PrivateKeyInfo`] encoded as ASN.1 DER -/// with the invariant that the contained-document is "well-formed", i.e. it -/// will parse successfully according to this crate's parsing rules. -#[derive(Clone)] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub struct PrivateKeyDocument(Zeroizing>); - -impl PrivateKeyDocument { - /// Parse the [`PrivateKeyInfo`] contained in this [`PrivateKeyDocument`] - pub fn private_key_info(&self) -> PrivateKeyInfo<'_> { - PrivateKeyInfo::try_from(self.0.as_ref()).expect("malformed PrivateKeyDocument") - } - - /// Parse [`PrivateKeyDocument`] from ASN.1 DER-encoded PKCS#8 - pub fn from_der(bytes: &[u8]) -> Result { - bytes.try_into() - } - - /// Parse [`PrivateKeyDocument`] from PEM-encoded PKCS#8. - /// - /// PEM-encoded private keys can be identified by the leading delimiter: - /// - /// ```text - /// -----BEGIN PRIVATE KEY----- - /// ``` - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn from_pem(s: &str) -> Result { - let (label, der_bytes) = pem::decode_vec(s.as_bytes())?; - - if label != PEM_TYPE_LABEL { - return Err(pem::Error::Label.into()); - } - - Self::from_der(&*der_bytes) - } - - /// Serialize [`PrivateKeyDocument`] as self-zeroizing PEM-encoded PKCS#8 string. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem(&self) -> Zeroizing { - self.to_pem_with_le(LineEnding::default()) - } - - /// Serialize [`PrivateKeyDocument`] as self-zeroizing PEM-encoded PKCS#8 string - /// with the given [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem_with_le(&self, line_ending: LineEnding) -> Zeroizing { - Zeroizing::new( - pem::encode_string(PEM_TYPE_LABEL, line_ending, &self.0) - .expect(error::PEM_ENCODING_MSG), - ) - } - - /// Load [`PrivateKeyDocument`] from an ASN.1 DER-encoded file on the local - /// filesystem (binary format). - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn read_der_file(path: impl AsRef) -> Result { - fs::read(path)?.try_into() - } - - /// Load [`PrivateKeyDocument`] from a PEM-encoded file on the local filesystem. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn read_pem_file(path: impl AsRef) -> Result { - Self::from_pem(&Zeroizing::new(fs::read_to_string(path)?)) - } - - /// Write ASN.1 DER-encoded PKCS#8 private key to the given path - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn write_der_file(&self, path: impl AsRef) -> Result<()> { - write_secret_file(path, self.as_ref()) - } - - /// Write PEM-encoded PKCS#8 private key to the given path - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn write_pem_file(&self, path: impl AsRef) -> Result<()> { - write_secret_file(path, self.to_pem().as_bytes()) - } - - /// Encrypt this private key using a symmetric encryption key derived - /// from the provided password. - /// - /// Uses the following algorithms for encryption: - /// - PBKDF: scrypt with default parameters: - /// - log₂(N): 15 - /// - r: 8 - /// - p: 1 - /// - Cipher: AES-256-CBC (best available option for PKCS#5 encryption) - #[cfg(feature = "encryption")] - #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))] - pub fn encrypt( - &self, - mut rng: impl CryptoRng + RngCore, - password: impl AsRef<[u8]>, - ) -> Result { - let mut salt = [0u8; 16]; - rng.fill_bytes(&mut salt); - - let mut iv = [0u8; 16]; - rng.fill_bytes(&mut iv); - - let pbes2_params = pbes2::Parameters::scrypt_aes256cbc(Default::default(), &salt, &iv) - .map_err(|_| Error::Crypto)?; - - self.encrypt_with_params(pbes2_params, password) - } - - /// Encrypt this private key using a symmetric encryption key derived - /// from the provided password and [`pbes2::Parameters`]. - #[cfg(feature = "encryption")] - #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))] - pub fn encrypt_with_params( - &self, - pbes2_params: pbes2::Parameters<'_>, - password: impl AsRef<[u8]>, - ) -> Result { - pbes2_params - .encrypt(password, self.as_ref()) - .map(|encrypted_data| { - EncryptedPrivateKeyInfo { - encryption_algorithm: pbes2_params.into(), - encrypted_data: &encrypted_data, - } - .into() - }) - .map_err(|_| Error::Crypto) - } -} - -impl AsRef<[u8]> for PrivateKeyDocument { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl From> for PrivateKeyDocument { - fn from(private_key_info: PrivateKeyInfo<'_>) -> PrivateKeyDocument { - PrivateKeyDocument::from(&private_key_info) - } -} - -impl From<&PrivateKeyInfo<'_>> for PrivateKeyDocument { - fn from(private_key_info: &PrivateKeyInfo<'_>) -> PrivateKeyDocument { - private_key_info - .to_vec() - .ok() - .and_then(|buf| buf.try_into().ok()) - .expect(error::DER_ENCODING_MSG) - } -} - -impl TryFrom<&[u8]> for PrivateKeyDocument { - type Error = Error; - - fn try_from(bytes: &[u8]) -> Result { - // Ensure document is well-formed - PrivateKeyInfo::try_from(bytes)?; - Ok(Self(Zeroizing::new(bytes.to_owned()))) - } -} - -impl TryFrom> for PrivateKeyDocument { - type Error = Error; - - fn try_from(mut bytes: Vec) -> Result { - // Ensure document is well-formed - if let Err(err) = PrivateKeyInfo::try_from(bytes.as_slice()) { - bytes.zeroize(); - return Err(err); - } - - Ok(Self(Zeroizing::new(bytes))) - } -} - -impl fmt::Debug for PrivateKeyDocument { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_tuple("PrivateKeyDocument") - .field(&self.private_key_info()) - .finish() - } -} - -#[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -impl FromStr for PrivateKeyDocument { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::from_pem(s) - } -} - -/// Write a file containing secret data to the filesystem, restricting the -/// file permissions so it's only readable by the owner -#[cfg(all(unix, feature = "std"))] -pub(super) fn write_secret_file(path: impl AsRef, data: &[u8]) -> Result<()> { - use std::{io::Write, os::unix::fs::OpenOptionsExt}; - - /// File permissions for secret data - #[cfg(unix)] - const SECRET_FILE_PERMS: u32 = 0o600; - - fs::OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .mode(SECRET_FILE_PERMS) - .open(path) - .and_then(|mut file| file.write_all(data))?; - - Ok(()) -} - -/// Write a file containing secret data to the filesystem -// TODO(tarcieri): permissions hardening on Windows -#[cfg(all(not(unix), feature = "std"))] -pub(super) fn write_secret_file(path: impl AsRef, data: &[u8]) -> Result<()> { - fs::write(path, data)?; - Ok(()) -} diff --git a/pkcs8/src/document/public_key.rs b/pkcs8/src/document/public_key.rs deleted file mode 100644 index 49b9ffd1..00000000 --- a/pkcs8/src/document/public_key.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! SPKI public key document. - -use crate::{error, Error, Result, SubjectPublicKeyInfo}; -use alloc::{borrow::ToOwned, vec::Vec}; -use core::{ - convert::{TryFrom, TryInto}, - fmt, -}; -use der::Encodable; - -#[cfg(feature = "std")] -use std::{fs, path::Path, str}; - -#[cfg(feature = "pem")] -use { - crate::{pem, LineEnding}, - alloc::string::String, - core::str::FromStr, -}; - -/// Type label for PEM-encoded private keys. -#[cfg(feature = "pem")] -pub(crate) const PEM_TYPE_LABEL: &str = "PUBLIC KEY"; - -/// SPKI public key document. -/// -/// This type provides storage for [`SubjectPublicKeyInfo`] encoded as ASN.1 -/// DER with the invariant that the contained-document is "well-formed", i.e. -/// it will parse successfully according to this crate's parsing rules. -#[derive(Clone)] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub struct PublicKeyDocument(Vec); - -impl PublicKeyDocument { - /// Parse the [`SubjectPublicKeyInfo`] contained in this [`PublicKeyDocument`] - pub fn spki(&self) -> SubjectPublicKeyInfo<'_> { - SubjectPublicKeyInfo::try_from(self.0.as_slice()).expect("malformed PublicKeyDocument") - } - - /// Parse [`PublicKeyDocument`] from ASN.1 DER. - pub fn from_der(bytes: &[u8]) -> Result { - bytes.try_into() - } - - /// Parse [`PublicKeyDocument`] from PEM. - /// - /// PEM-encoded public keys can be identified by the leading delimiter: - /// - /// ```text - /// -----BEGIN PUBLIC KEY----- - /// ``` - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn from_pem(s: &str) -> Result { - let (label, der_bytes) = pem::decode_vec(s.as_bytes())?; - - if label != PEM_TYPE_LABEL { - return Err(pem::Error::Label.into()); - } - - Self::from_der(&*der_bytes) - } - - /// Serialize [`PublicKeyDocument`] as PEM-encoded PKCS#8 (SPKI) string. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem(&self) -> String { - self.to_pem_with_le(LineEnding::default()) - } - - /// Serialize [`PublicKeyDocument`] as PEM-encoded PKCS#8 (SPKI) string - /// with the given [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem_with_le(&self, line_ending: LineEnding) -> String { - pem::encode_string(PEM_TYPE_LABEL, line_ending, &self.0).expect(error::PEM_ENCODING_MSG) - } - - /// Load [`PublicKeyDocument`] from an ASN.1 DER-encoded file on the local - /// filesystem (binary format). - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn read_der_file(path: impl AsRef) -> Result { - fs::read(path)?.try_into() - } - - /// Load [`PublicKeyDocument`] from a PEM-encoded file on the local filesystem. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn read_pem_file(path: impl AsRef) -> Result { - Self::from_pem(&fs::read_to_string(path)?) - } - - /// Write ASN.1 DER-encoded public key to the given path - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn write_der_file(&self, path: impl AsRef) -> Result<()> { - fs::write(path, self.as_ref())?; - Ok(()) - } - - /// Write PEM-encoded public key to the given path - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - pub fn write_pem_file(&self, path: impl AsRef) -> Result<()> { - fs::write(path, self.to_pem().as_bytes())?; - Ok(()) - } -} - -impl AsRef<[u8]> for PublicKeyDocument { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl From> for PublicKeyDocument { - fn from(spki: SubjectPublicKeyInfo<'_>) -> PublicKeyDocument { - PublicKeyDocument::from(&spki) - } -} - -impl From<&SubjectPublicKeyInfo<'_>> for PublicKeyDocument { - fn from(spki: &SubjectPublicKeyInfo<'_>) -> PublicKeyDocument { - spki.to_vec() - .ok() - .and_then(|buf| buf.try_into().ok()) - .expect(error::DER_ENCODING_MSG) - } -} - -impl TryFrom<&[u8]> for PublicKeyDocument { - type Error = Error; - - fn try_from(bytes: &[u8]) -> Result { - // Ensure document is well-formed - SubjectPublicKeyInfo::try_from(bytes)?; - Ok(Self(bytes.to_owned())) - } -} - -impl TryFrom> for PublicKeyDocument { - type Error = Error; - - fn try_from(bytes: Vec) -> Result { - // Ensure document is well-formed - SubjectPublicKeyInfo::try_from(bytes.as_slice())?; - Ok(Self(bytes)) - } -} - -impl fmt::Debug for PublicKeyDocument { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_tuple("PublicKeyDocument") - .field(&self.spki()) - .finish() - } -} - -#[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -impl FromStr for PublicKeyDocument { - type Err = Error; - - fn from_str(s: &str) -> Result { - Self::from_pem(s) - } -} diff --git a/pkcs8/src/encrypted_private_key_info.rs b/pkcs8/src/encrypted_private_key_info.rs deleted file mode 100644 index 83d2bdee..00000000 --- a/pkcs8/src/encrypted_private_key_info.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! PKCS#8 `EncryptedPrivateKeyInfo` - -use crate::{Error, Result}; -use core::{convert::TryFrom, fmt}; -use der::{ - asn1::{Any, OctetString}, - Decodable, Encodable, Message, -}; -use pkcs5::EncryptionScheme; - -#[cfg(feature = "alloc")] -use crate::{EncryptedPrivateKeyDocument, PrivateKeyDocument}; - -#[cfg(feature = "encryption")] -use core::convert::TryInto; - -#[cfg(feature = "pem")] -use { - crate::{error, pem, LineEnding}, - zeroize::Zeroizing, -}; - -/// Type label for PEM-encoded private keys. -#[cfg(feature = "pem")] -pub(crate) const PEM_TYPE_LABEL: &str = "ENCRYPTED PRIVATE KEY"; - -/// PKCS#8 `EncryptedPrivateKeyInfo`. -/// -/// ASN.1 structure containing a PKCS#5 [`EncryptionScheme`] identifier for a -/// password-based symmetric encryption scheme and encrypted private key data. -/// -/// ## Schema -/// Structure described in [RFC 5208 Section 6]: -/// -/// ```text -/// EncryptedPrivateKeyInfo ::= SEQUENCE { -/// encryptionAlgorithm EncryptionAlgorithmIdentifier, -/// encryptedData EncryptedData } -/// -/// EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier -/// -/// EncryptedData ::= OCTET STRING -/// ``` -/// -/// [RFC 5208 Section 6]: https://tools.ietf.org/html/rfc5208#section-6 -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs5")))] -#[derive(Clone, Eq, PartialEq)] -pub struct EncryptedPrivateKeyInfo<'a> { - /// Algorithm identifier describing a password-based symmetric encryption - /// scheme used to encrypt the `encrypted_data` field. - pub encryption_algorithm: EncryptionScheme<'a>, - - /// Private key data - pub encrypted_data: &'a [u8], -} - -impl<'a> EncryptedPrivateKeyInfo<'a> { - /// Attempt to decrypt this encrypted private key using the provided - /// password to derive an encryption key. - #[cfg(feature = "encryption")] - #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))] - pub fn decrypt(&self, password: impl AsRef<[u8]>) -> Result { - self.encryption_algorithm - .decrypt(password, &self.encrypted_data) - .map_err(|_| Error::Crypto) - .and_then(TryInto::try_into) - } - - /// Encode this [`EncryptedPrivateKeyInfo`] as ASN.1 DER. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - pub fn to_der(&self) -> EncryptedPrivateKeyDocument { - self.into() - } - - /// Encode this [`EncryptedPrivateKeyInfo`] as PEM-encoded ASN.1 DER. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem(&self) -> Zeroizing { - self.to_pem_with_le(LineEnding::default()) - } - - /// Encode this [`EncryptedPrivateKeyInfo`] as PEM-encoded ASN.1 DER with - /// the given [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem_with_le(&self, line_ending: LineEnding) -> Zeroizing { - Zeroizing::new( - pem::encode_string(PEM_TYPE_LABEL, line_ending, self.to_der().as_ref()) - .expect(error::PEM_ENCODING_MSG), - ) - } -} - -impl<'a> TryFrom<&'a [u8]> for EncryptedPrivateKeyInfo<'a> { - type Error = Error; - - fn try_from(bytes: &'a [u8]) -> Result { - Ok(Self::from_der(bytes)?) - } -} - -impl<'a> TryFrom> for EncryptedPrivateKeyInfo<'a> { - type Error = der::Error; - - fn try_from(any: Any<'a>) -> der::Result> { - any.sequence(|decoder| { - Ok(Self { - encryption_algorithm: decoder.decode()?, - encrypted_data: decoder.octet_string()?.as_bytes(), - }) - }) - } -} - -impl<'a> Message<'a> for EncryptedPrivateKeyInfo<'a> { - fn fields(&self, f: F) -> der::Result - where - F: FnOnce(&[&dyn Encodable]) -> der::Result, - { - f(&[ - &self.encryption_algorithm, - &OctetString::new(self.encrypted_data)?, - ]) - } -} - -impl<'a> fmt::Debug for EncryptedPrivateKeyInfo<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("EncryptedPrivateKeyInfo") - .field("encryption_algorithm", &self.encryption_algorithm) - .finish() // TODO(tarcieri): use `finish_non_exhaustive` when stable - } -} diff --git a/pkcs8/src/error.rs b/pkcs8/src/error.rs deleted file mode 100644 index 788246f0..00000000 --- a/pkcs8/src/error.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! Error types - -use core::fmt; - -/// Message to display when an `expect`-ed DER encoding error occurs -#[cfg(feature = "alloc")] -pub(crate) const DER_ENCODING_MSG: &str = "DER encoding error"; - -/// Message to display when an `expect`-ed PEM encoding error occurs -#[cfg(feature = "pem")] -pub(crate) const PEM_ENCODING_MSG: &str = "PEM encoding error"; - -/// Result type -pub type Result = core::result::Result; - -/// Error type -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -#[non_exhaustive] -pub enum Error { - /// ASN.1 DER-related errors. - Asn1(der::Error), - - /// Cryptographic errors. - /// - /// This is primarily used for relaying PKCS#5-related errors for - /// PKCS#8 documents which have been encrypted under a password. - Crypto, - - /// File not found error. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - FileNotFound, - - /// Malformed cryptographic key contained in a PKCS#8 document. - /// - /// This is intended for relaying errors related to the raw data contained - /// within [`PrivateKeyInfo::private_key`][`crate::PrivateKeyInfo::private_key`] - /// or [`SubjectPublicKeyInfo::subject_public_key`][`crate::SubjectPublicKeyInfo::subject_public_key`]. - KeyMalformed, - - /// I/O errors. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - Io, - - /// [`AlgorithmIdentifier::parameters`][`crate::AlgorithmIdentifier::parameters`] - /// is malformed or otherwise encoded in an unexpected manner. - ParametersMalformed, - - /// PEM encoding errors. - // TODO(tarcieri): propagate `pem_rfc7468::Error` - #[cfg(feature = "pem")] - Pem, - - /// Permission denied reading file. - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - PermissionDenied, - - /// PKCS#1 errors. - #[cfg(feature = "pkcs1")] - #[cfg_attr(docsrs, doc(cfg(feature = "pkcs1")))] - Pkcs1(pkcs1::Error), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::Asn1(err) => write!(f, "PKCS#8 ASN.1 error: {}", err), - Error::Crypto => f.write_str("PKCS#8 cryptographic error"), - #[cfg(feature = "std")] - Error::FileNotFound => f.write_str("file not found"), - Error::KeyMalformed => f.write_str("PKCS#8 cryptographic key data malformed"), - #[cfg(feature = "std")] - Error::Io => f.write_str("I/O error"), - Error::ParametersMalformed => f.write_str("PKCS#8 algorithm parameters malformed"), - #[cfg(feature = "pem")] - Error::Pem => f.write_str("PKCS#8 PEM error"), - #[cfg(feature = "std")] - Error::PermissionDenied => f.write_str("permission denied"), - #[cfg(feature = "pkcs1")] - Error::Pkcs1(err) => write!(f, "{}", err), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Error {} - -impl From for Error { - fn from(err: der::Error) -> Error { - Error::Asn1(err) - } -} - -impl From for Error { - fn from(err: der::ErrorKind) -> Error { - Error::Asn1(err.into()) - } -} - -#[cfg(feature = "pem")] -impl From for Error { - fn from(_: pem_rfc7468::Error) -> Error { - // TODO(tarcieri): propagate `pem_rfc7468::Error` - Error::Pem - } -} - -#[cfg(feature = "pkcs1")] -impl From for Error { - fn from(err: pkcs1::Error) -> Error { - Error::Pkcs1(err) - } -} - -#[cfg(feature = "std")] -impl From for Error { - fn from(err: std::io::Error) -> Error { - match err.kind() { - std::io::ErrorKind::NotFound => Error::FileNotFound, - std::io::ErrorKind::PermissionDenied => Error::PermissionDenied, - _ => Error::Io, - } - } -} diff --git a/pkcs8/src/lib.rs b/pkcs8/src/lib.rs deleted file mode 100644 index 287638ac..00000000 --- a/pkcs8/src/lib.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8: -//! Private-Key Information Syntax Specification ([RFC 5208]), with additional -//! support for PKCS#8v2 asymmetric key packages ([RFC 5958]) -//! -//! # About -//! This library provides generalized PKCS#8 support designed to work with a -//! number of different algorithms. It supports `no_std` platforms including -//! ones without a heap (albeit with reduced functionality). -//! -//! It supports decoding/encoding the following types: -//! -//! - [`EncryptedPrivateKeyInfo`]: (with `pkcs5` feature) encrypted key. -//! - [`PrivateKeyInfo`]: algorithm identifier and data representing a private key. -//! Optionally also includes public key data for asymmetric keys. -//! - [`SubjectPublicKeyInfo`]: algorithm identifier and data representing a public key -//! (re-exported from the [`spki`] crate) -//! -//! When the `alloc` feature is enabled, the following additional types are -//! available which provide more convenient decoding/encoding support: -//! -//! - [`EncryptedPrivateKeyDocument`]: (with `pkcs5` feature) heap-backed encrypted key. -//! - [`PrivateKeyDocument`]: heap-backed storage for serialized [`PrivateKeyInfo`]. -//! - [`PublicKeyDocument`]: heap-backed storage for serialized [`SubjectPublicKeyInfo`]. -//! -//! When the `pem` feature is enabled, it also supports decoding/encoding -//! documents from "PEM encoding" format as defined in RFC 7468. -//! -//! # Supported Algorithms -//! This crate has been written generically so it can be used to implement -//! PKCS#8 support for any algorithm. -//! -//! However, it's only tested against keys generated by OpenSSL for the -//! following algorithms: -//! -//! - ECC (`id-ecPublicKey`) -//! - Ed25519 (`Ed25519`) -//! - RSA (`rsaEncryption`) -//! -//! Please open an issue if you encounter trouble using it with other -//! algorithms. -//! -//! # Encrypted Private Key Support -//! [`EncryptedPrivateKeyInfo`] supports decoding/encoding encrypted PKCS#8 -//! private keys and is gated under the `pkcs5` feature. The corresponding -//! [`EncryptedPrivateKeyDocument`] type provides heap-backed storage -//! (`alloc` feature required). -//! -//! When the `encryption` feature of this crate is enabled, it provides -//! [`EncryptedPrivateKeyInfo::decrypt`] and [`PrivateKeyInfo::encrypt`] -//! functions which are able to decrypt/encrypt keys using the following -//! algorithms: -//! -//! - [PKCS#5v2 Password Based Encryption Scheme 2 (RFC 8018)] -//! - Key derivation function: [scrypt] ([RFC 7914], also supports PBKDF2-HMAC-SHA256) -//! - Symmetric encryption: AES-128-CBC or AES-256-CBC (best available options for PKCS#5v2) -//! -//! # PKCS#1 support (optional) -//! When the `pkcs1` feature of this crate is enabled, this crate provides -//! a blanket impl of PKCS#8 support for types which impl the traits from the -//! [`pkcs1`] crate (e.g. `FromRsaPrivateKey`, `ToRsaPrivateKey`). -//! -//! # Minimum Supported Rust Version -//! -//! This crate requires **Rust 1.51** at a minimum. -//! -//! [RFC 5208]: https://tools.ietf.org/html/rfc5208 -//! [RFC 5958]: https://tools.ietf.org/html/rfc5958 -//! [RFC 7914]: https://datatracker.ietf.org/doc/html/rfc7914 -//! [PKCS#5v2 Password Based Encryption Scheme 2 (RFC 8018)]: https://tools.ietf.org/html/rfc8018#section-6.2 -//! [scrypt]: https://en.wikipedia.org/wiki/Scrypt - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/pkcs8/0.7.5" -)] -#![forbid(unsafe_code, clippy::unwrap_used)] -#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] - -#[cfg(feature = "alloc")] -extern crate alloc; -#[cfg(feature = "std")] -extern crate std; - -mod attributes; -mod error; -mod private_key_info; -mod traits; -mod version; - -#[cfg(feature = "alloc")] -mod document; - -#[cfg(feature = "pkcs5")] -pub(crate) mod encrypted_private_key_info; - -pub use crate::{ - attributes::Attributes, - error::{Error, Result}, - private_key_info::PrivateKeyInfo, - traits::{FromPrivateKey, FromPublicKey}, - version::Version, -}; -pub use der::{self, asn1::ObjectIdentifier}; -pub use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; - -#[cfg(feature = "alloc")] -pub use crate::{ - document::{private_key::PrivateKeyDocument, public_key::PublicKeyDocument}, - traits::{ToPrivateKey, ToPublicKey}, -}; - -#[cfg(feature = "pem")] -#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] -pub use pem_rfc7468::LineEnding; - -#[cfg(feature = "pkcs5")] -pub use encrypted_private_key_info::EncryptedPrivateKeyInfo; - -#[cfg(feature = "pkcs1")] -pub use pkcs1; - -#[cfg(feature = "pkcs5")] -pub use pkcs5; - -#[cfg(all(feature = "alloc", feature = "pkcs5"))] -pub use crate::document::encrypted_private_key::EncryptedPrivateKeyDocument; - -#[cfg(feature = "pem")] -use pem_rfc7468 as pem; diff --git a/pkcs8/src/private_key_info.rs b/pkcs8/src/private_key_info.rs deleted file mode 100644 index e09db5f5..00000000 --- a/pkcs8/src/private_key_info.rs +++ /dev/null @@ -1,262 +0,0 @@ -//! PKCS#8 `PrivateKeyInfo`. - -use crate::{AlgorithmIdentifier, Attributes, Error, Result, Version}; -use core::{ - convert::{TryFrom, TryInto}, - fmt, -}; -use der::{ - asn1::{Any, BitString, ContextSpecific, OctetString}, - Decodable, Encodable, Message, TagNumber, -}; - -#[cfg(feature = "alloc")] -use crate::PrivateKeyDocument; - -#[cfg(feature = "encryption")] -use { - crate::EncryptedPrivateKeyDocument, - rand_core::{CryptoRng, RngCore}, -}; - -#[cfg(feature = "pem")] -use { - crate::{error, pem, LineEnding}, - alloc::string::String, - zeroize::Zeroizing, -}; - -/// Context-specific tag number for [`Attributes`]. -const ATTRIBUTES_TAG: TagNumber = TagNumber::new(0); - -/// Context-specific tag number for the public key. -const PUBLIC_KEY_TAG: TagNumber = TagNumber::new(1); - -/// Type label for PEM-encoded private keys. -#[cfg(feature = "pem")] -pub(crate) const PEM_TYPE_LABEL: &str = "PRIVATE KEY"; - -/// PKCS#8 `PrivateKeyInfo`. -/// -/// ASN.1 structure containing an [`AlgorithmIdentifier`], private key -/// data in an algorithm specific format, and optional attributes. -/// -/// Supports PKCS#8 v1 as described in [RFC 5208] and PKCS#8 v2 as described -/// in [RFC 5958]. PKCS#8 v2 keys include an additional public key field. -/// -/// # PKCS#8 v1 `PrivateKeyInfo` -/// -/// Described in [RFC 5208 Section 5]: -/// -/// ```text -/// PrivateKeyInfo ::= SEQUENCE { -/// version Version, -/// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, -/// privateKey PrivateKey, -/// attributes [0] IMPLICIT Attributes OPTIONAL } -/// -/// Version ::= INTEGER -/// -/// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier -/// -/// PrivateKey ::= OCTET STRING -/// -/// Attributes ::= SET OF Attribute -/// ``` -/// -/// # PKCS#8 v2 `OneAsymmetricKey` -/// -/// PKCS#8 `OneAsymmetricKey` as described in [RFC 5958 Section 2]: -/// -/// ```text -/// PrivateKeyInfo ::= OneAsymmetricKey -/// -/// OneAsymmetricKey ::= SEQUENCE { -/// version Version, -/// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, -/// privateKey PrivateKey, -/// attributes [0] Attributes OPTIONAL, -/// ..., -/// [[2: publicKey [1] PublicKey OPTIONAL ]], -/// ... -/// } -/// -/// Version ::= INTEGER { v1(0), v2(1) } (v1, ..., v2) -/// -/// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier -/// -/// PrivateKey ::= OCTET STRING -/// -/// Attributes ::= SET OF Attribute -/// -/// PublicKey ::= BIT STRING -/// ``` -/// -/// [RFC 5208]: https://tools.ietf.org/html/rfc5208 -/// [RFC 5958]: https://datatracker.ietf.org/doc/html/rfc5958 -/// [RFC 5208 Section 5]: https://tools.ietf.org/html/rfc5208#section-5 -/// [RFC 5958 Section 2]: https://datatracker.ietf.org/doc/html/rfc5958#section-2 -#[derive(Clone)] -pub struct PrivateKeyInfo<'a> { - /// X.509 [`AlgorithmIdentifier`] for the private key type. - pub algorithm: AlgorithmIdentifier<'a>, - - /// Private key data. - pub private_key: &'a [u8], - - /// Attributes. - pub attributes: Option>, - - /// Public key data, optionally available if version is V2. - pub public_key: Option<&'a [u8]>, -} - -impl<'a> PrivateKeyInfo<'a> { - /// Create a new PKCS#8 [`PrivateKeyInfo`] message. - /// - /// This is a helper method which initializes `attributes` and `public_key` - /// to `None`, helpful if you aren't using those. - pub fn new(algorithm: AlgorithmIdentifier<'a>, private_key: &'a [u8]) -> Self { - Self { - algorithm, - private_key, - attributes: None, - public_key: None, - } - } - - /// Get the PKCS#8 [`Version`] for this structure. - /// - /// [`Version::V1`] if `public_key` is `None`, [`Version::V2`] if `Some`. - pub fn version(&self) -> Version { - if self.public_key.is_some() { - Version::V2 - } else { - Version::V1 - } - } - - /// Encrypt this private key using a symmetric encryption key derived - /// from the provided password. - /// - /// See [`PrivateKeyDocument::encrypt`] for more information. - #[cfg(feature = "encryption")] - #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))] - pub fn encrypt( - &self, - rng: impl CryptoRng + RngCore, - password: impl AsRef<[u8]>, - ) -> Result { - PrivateKeyDocument::from(self).encrypt(rng, password) - } - - /// Encode this [`PrivateKeyInfo`] as ASN.1 DER. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - pub fn to_der(&self) -> PrivateKeyDocument { - self.into() - } - - /// Encode this [`PrivateKeyInfo`] as PEM-encoded ASN.1 DER. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem(&self) -> Zeroizing { - self.to_pem_with_le(LineEnding::default()) - } - - /// Encode this [`PrivateKeyInfo`] as PEM-encoded ASN.1 DER with the given - /// [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - pub fn to_pem_with_le(&self, line_ending: LineEnding) -> Zeroizing { - Zeroizing::new( - pem::encode_string(PEM_TYPE_LABEL, line_ending, self.to_der().as_ref()) - .expect(error::PEM_ENCODING_MSG), - ) - } -} - -impl<'a> TryFrom<&'a [u8]> for PrivateKeyInfo<'a> { - type Error = Error; - - fn try_from(bytes: &'a [u8]) -> Result { - Ok(Self::from_der(bytes)?) - } -} - -impl<'a> TryFrom> for PrivateKeyInfo<'a> { - type Error = der::Error; - - fn try_from(any: Any<'a>) -> der::Result> { - any.sequence(|decoder| { - // Parse and validate `version` INTEGER. - let version = Version::decode(decoder)?; - let algorithm = decoder.decode()?; - let private_key = decoder.octet_string()?.into(); - - let attributes = decoder - .context_specific(ATTRIBUTES_TAG)? - .map(TryInto::try_into) - .transpose()?; - - let public_key = decoder - .context_specific(PUBLIC_KEY_TAG)? - .map(|any| any.bit_string()) - .transpose()? - .map(|bs| bs.as_bytes()); - - if version.has_public_key() != public_key.is_some() { - return Err(decoder.value_error(der::Tag::ContextSpecific(PUBLIC_KEY_TAG))); - } - - // Ignore any remaining extension fields - while !decoder.is_finished() { - decoder.decode::>()?; - } - - Ok(Self { - algorithm, - private_key, - attributes, - public_key, - }) - }) - } -} - -impl<'a> Message<'a> for PrivateKeyInfo<'a> { - fn fields(&self, f: F) -> der::Result - where - F: FnOnce(&[&dyn Encodable]) -> der::Result, - { - f(&[ - &u8::from(self.version()), - &self.algorithm, - &OctetString::new(self.private_key)?, - &self.attributes.map(|value| ContextSpecific { - tag_number: ATTRIBUTES_TAG, - value: value.into(), - }), - &self - .public_key - .map(|pk| { - BitString::new(pk).map(|value| ContextSpecific { - tag_number: PUBLIC_KEY_TAG, - value: value.into(), - }) - }) - .transpose()?, - ]) - } -} - -impl<'a> fmt::Debug for PrivateKeyInfo<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PrivateKeyInfo") - .field("version", &self.version()) - .field("algorithm", &self.algorithm) - .field("attributes", &self.attributes) - .field("public_key", &self.public_key) - .finish() // TODO: use `finish_non_exhaustive` when stable - } -} diff --git a/pkcs8/src/traits.rs b/pkcs8/src/traits.rs deleted file mode 100644 index 3146e33f..00000000 --- a/pkcs8/src/traits.rs +++ /dev/null @@ -1,323 +0,0 @@ -//! Traits for parsing objects from PKCS#8 encoded documents - -use crate::{PrivateKeyInfo, Result, SubjectPublicKeyInfo}; -use core::convert::TryFrom; - -#[cfg(feature = "alloc")] -use crate::{PrivateKeyDocument, PublicKeyDocument}; - -#[cfg(feature = "encryption")] -use { - crate::{EncryptedPrivateKeyDocument, EncryptedPrivateKeyInfo}, - rand_core::{CryptoRng, RngCore}, -}; - -#[cfg(feature = "pem")] -use {crate::LineEnding, alloc::string::String}; - -#[cfg(feature = "pkcs1")] -use crate::{Error, ObjectIdentifier}; - -#[cfg(feature = "std")] -use std::path::Path; - -#[cfg(any(feature = "pem", feature = "std"))] -use zeroize::Zeroizing; - -#[cfg(all(feature = "alloc", feature = "pkcs1"))] -use crate::AlgorithmIdentifier; - -/// PKCS#1 RSA Algorithm [`ObjectIdentifier`]. -/// -/// -#[cfg(feature = "pkcs1")] -const PKCS1_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.1.1"); - -/// Parse a private key object from a PKCS#8 encoded document. -pub trait FromPrivateKey: Sized { - /// Parse the [`PrivateKeyInfo`] from a PKCS#8-encoded document. - fn from_pkcs8_private_key_info(private_key_info: PrivateKeyInfo<'_>) -> Result; - - /// Deserialize PKCS#8 private key from ASN.1 DER-encoded data - /// (binary format). - fn from_pkcs8_der(bytes: &[u8]) -> Result { - Self::from_pkcs8_private_key_info(PrivateKeyInfo::try_from(bytes)?) - } - - /// Deserialize encrypted PKCS#8 private key from ASN.1 DER-encoded data - /// (binary format) and attempt to decrypt it using the provided password. - #[cfg(feature = "encryption")] - #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))] - fn from_pkcs8_encrypted_der(bytes: &[u8], password: impl AsRef<[u8]>) -> Result { - EncryptedPrivateKeyInfo::try_from(bytes)? - .decrypt(password) - .and_then(|doc| Self::from_pkcs8_doc(&doc)) - } - - /// Deserialize PKCS#8 private key from a [`PrivateKeyDocument`]. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn from_pkcs8_doc(doc: &PrivateKeyDocument) -> Result { - Self::from_pkcs8_private_key_info(doc.private_key_info()) - } - - /// Deserialize PKCS#8-encoded private key from PEM. - /// - /// Keys in this format begin with the following delimiter: - /// - /// ```text - /// -----BEGIN PRIVATE KEY----- - /// ``` - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn from_pkcs8_pem(s: &str) -> Result { - PrivateKeyDocument::from_pem(s).and_then(|doc| Self::from_pkcs8_doc(&doc)) - } - - /// Deserialize encrypted PKCS#8-encoded private key from PEM and attempt - /// to decrypt it using the provided password. - /// - /// Keys in this format begin with the following delimiter: - /// - /// ```text - /// -----BEGIN ENCRYPTED PRIVATE KEY----- - /// ``` - #[cfg(all(feature = "encryption", feature = "pem"))] - #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn from_pkcs8_encrypted_pem(s: &str, password: impl AsRef<[u8]>) -> Result { - EncryptedPrivateKeyDocument::from_pem(s)? - .decrypt(password) - .and_then(|doc| Self::from_pkcs8_doc(&doc)) - } - - /// Load PKCS#8 private key from an ASN.1 DER-encoded file on the local - /// filesystem (binary format). - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs8_der_file(path: impl AsRef) -> Result { - PrivateKeyDocument::read_der_file(path).and_then(|doc| Self::from_pkcs8_doc(&doc)) - } - - /// Load PKCS#8 private key from a PEM-encoded file on the local filesystem. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_pkcs8_pem_file(path: impl AsRef) -> Result { - PrivateKeyDocument::read_pem_file(path).and_then(|doc| Self::from_pkcs8_doc(&doc)) - } -} - -/// Parse a public key object from an encoded SPKI document. -pub trait FromPublicKey: Sized { - /// Parse [`SubjectPublicKeyInfo`] into a public key object. - fn from_spki(spki: SubjectPublicKeyInfo<'_>) -> Result; - - /// Deserialize object from ASN.1 DER-encoded [`SubjectPublicKeyInfo`] - /// (binary format). - fn from_public_key_der(bytes: &[u8]) -> Result { - Self::from_spki(SubjectPublicKeyInfo::try_from(bytes)?) - } - - /// Deserialize PKCS#8 private key from a [`PrivateKeyDocument`]. - #[cfg(feature = "alloc")] - #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn from_public_key_doc(doc: &PublicKeyDocument) -> Result { - Self::from_spki(doc.spki()) - } - - /// Deserialize PEM-encoded [`SubjectPublicKeyInfo`]. - /// - /// Keys in this format begin with the following delimiter: - /// - /// ```text - /// -----BEGIN PUBLIC KEY----- - /// ``` - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn from_public_key_pem(s: &str) -> Result { - PublicKeyDocument::from_pem(s).and_then(|doc| Self::from_public_key_doc(&doc)) - } - - /// Load public key object from an ASN.1 DER-encoded file on the local - /// filesystem (binary format). - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_public_key_der_file(path: impl AsRef) -> Result { - PublicKeyDocument::read_der_file(path).and_then(|doc| Self::from_public_key_doc(&doc)) - } - - /// Load public key object from a PEM-encoded file on the local filesystem. - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn read_public_key_pem_file(path: impl AsRef) -> Result { - PublicKeyDocument::read_pem_file(path).and_then(|doc| Self::from_public_key_doc(&doc)) - } -} - -/// Serialize a private key object to a PKCS#8 encoded document. -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub trait ToPrivateKey { - /// Serialize a [`PrivateKeyDocument`] containing a PKCS#8-encoded private key. - fn to_pkcs8_der(&self) -> Result; - - /// Create an [`EncryptedPrivateKeyDocument`] containing the ciphertext of - /// a PKCS#8 encoded private key encrypted under the given `password`. - #[cfg(feature = "encryption")] - #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))] - fn to_pkcs8_encrypted_der( - &self, - rng: impl CryptoRng + RngCore, - password: impl AsRef<[u8]>, - ) -> Result { - self.to_pkcs8_der()?.encrypt(rng, password) - } - - /// Serialize this private key as PEM-encoded PKCS#8. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_pkcs8_pem(&self) -> Result> { - self.to_pkcs8_pem_with_le(LineEnding::default()) - } - - /// Serialize this private key as PEM-encoded PKCS#8 with the given [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_pkcs8_pem_with_le(&self, line_ending: LineEnding) -> Result> { - Ok(self.to_pkcs8_der()?.to_pem_with_le(line_ending)) - } - - /// Serialize this private key as an encrypted PEM-encoded PKCS#8 private - /// key using the `provided` to derive an encryption key. - #[cfg(all(feature = "encryption", feature = "pem"))] - #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_pkcs8_encrypted_pem( - &self, - rng: impl CryptoRng + RngCore, - password: impl AsRef<[u8]>, - ) -> Result> { - self.to_pkcs8_encrypted_der(rng, password) - .map(|key| key.to_pem()) - } - - /// Write ASN.1 DER-encoded PKCS#8 private key to the given path - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs8_der_file(&self, path: impl AsRef) -> Result<()> { - self.to_pkcs8_der()?.write_der_file(path) - } - - /// Write ASN.1 DER-encoded PKCS#8 private key to the given path - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_pkcs8_pem_file(&self, path: impl AsRef) -> Result<()> { - self.to_pkcs8_der()?.write_pem_file(path) - } -} - -/// Serialize a public key object to a SPKI-encoded document. -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub trait ToPublicKey { - /// Serialize a [`PublicKeyDocument`] containing a SPKI-encoded public key. - fn to_public_key_der(&self) -> Result; - - /// Serialize this public key as PEM-encoded SPKI. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_public_key_pem(&self) -> Result { - self.to_public_key_pem_with_le(LineEnding::default()) - } - - /// Serialize this public key as PEM-encoded SPKI with the given [`LineEnding`]. - #[cfg(feature = "pem")] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - fn to_public_key_pem_with_le(&self, line_ending: LineEnding) -> Result { - Ok(self.to_public_key_der()?.to_pem_with_le(line_ending)) - } - - /// Write ASN.1 DER-encoded public key to the given path - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_public_key_der_file(&self, path: impl AsRef) -> Result<()> { - self.to_public_key_der()?.write_der_file(path) - } - - /// Write ASN.1 DER-encoded public key to the given path - #[cfg(all(feature = "pem", feature = "std"))] - #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] - fn write_public_key_pem_file(&self, path: impl AsRef) -> Result<()> { - self.to_public_key_der()?.write_pem_file(path) - } -} - -#[cfg(feature = "pkcs1")] -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs1")))] -impl FromPrivateKey for K { - fn from_pkcs8_private_key_info(pkcs8_key: PrivateKeyInfo<'_>) -> Result { - pkcs8_key.algorithm.assert_algorithm_oid(PKCS1_OID)?; - - if pkcs8_key.algorithm.parameters != Some(der::asn1::Null.into()) { - return Err(Error::ParametersMalformed); - } - - let pkcs1_key = pkcs1::RsaPrivateKey::try_from(pkcs8_key.private_key)?; - Ok(K::from_pkcs1_private_key(pkcs1_key)?) - } -} - -#[cfg(feature = "pkcs1")] -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs1")))] -impl FromPublicKey for K { - fn from_spki(pkcs8_key: SubjectPublicKeyInfo<'_>) -> Result { - pkcs8_key.algorithm.assert_algorithm_oid(PKCS1_OID)?; - - if pkcs8_key.algorithm.parameters != Some(der::asn1::Null.into()) { - return Err(Error::ParametersMalformed); - } - - let pkcs1_key = pkcs1::RsaPublicKey::try_from(pkcs8_key.subject_public_key)?; - Ok(K::from_pkcs1_public_key(pkcs1_key)?) - } -} - -#[cfg(all(feature = "alloc", feature = "pkcs1"))] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs1")))] -impl ToPrivateKey for K { - fn to_pkcs8_der(&self) -> Result { - let pkcs1_der = self.to_pkcs1_der()?; - - let algorithm = AlgorithmIdentifier { - oid: PKCS1_OID, - parameters: Some(der::asn1::Null.into()), - }; - - Ok(PrivateKeyInfo::new(algorithm, pkcs1_der.as_ref()).to_der()) - } -} - -#[cfg(all(feature = "alloc", feature = "pkcs1"))] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -#[cfg_attr(docsrs, doc(cfg(feature = "pkcs1")))] -impl ToPublicKey for K { - fn to_public_key_der(&self) -> Result { - let pkcs1_der = self.to_pkcs1_der()?; - - let algorithm = AlgorithmIdentifier { - oid: PKCS1_OID, - parameters: Some(der::asn1::Null.into()), - }; - - Ok(SubjectPublicKeyInfo { - algorithm, - subject_public_key: pkcs1_der.as_ref(), - } - .into()) - } -} diff --git a/pkcs8/src/version.rs b/pkcs8/src/version.rs deleted file mode 100644 index 3c6f034e..00000000 --- a/pkcs8/src/version.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! PKCS#8 version identifier. - -use core::convert::{TryFrom, TryInto}; -use der::{asn1::Any, Encodable, Encoder, Tag, Tagged}; - -use crate::Error; - -/// Version identifier for PKCS#8 documents. -/// -/// (RFC 5958 designates `0` and `1` as the only valid versions for PKCS#8 documents) -#[derive(Clone, Debug, Copy, PartialEq)] -pub enum Version { - /// Denotes PKCS#8 v1: no public key field. - V1 = 0, - - /// Denotes PKCS#8 v2: `OneAsymmetricKey` with public key field. - V2 = 1, -} - -impl Version { - /// Is this version expected to have a public key? - pub fn has_public_key(self) -> bool { - match self { - Version::V1 => false, - Version::V2 => true, - } - } -} - -impl From for u8 { - fn from(version: Version) -> Self { - version as u8 - } -} - -impl TryFrom for Version { - type Error = Error; - fn try_from(byte: u8) -> Result { - match byte { - 0 => Ok(Version::V1), - 1 => Ok(Version::V2), - _ => Err(Self::TAG.value_error().into()), - } - } -} - -impl<'a> TryFrom> for Version { - type Error = der::Error; - fn try_from(any: Any<'a>) -> der::Result { - u8::try_from(any)? - .try_into() - .map_err(|_| Self::TAG.value_error()) - } -} - -impl Encodable for Version { - fn encoded_len(&self) -> der::Result { - der::Length::from(1u8).for_tlv() - } - - fn encode(&self, encoder: &mut Encoder<'_>) -> der::Result<()> { - u8::from(*self).encode(encoder) - } -} - -impl Tagged for Version { - const TAG: Tag = Tag::Integer; -} diff --git a/pkcs8/tests/encrypted_private_key.rs b/pkcs8/tests/encrypted_private_key.rs deleted file mode 100644 index 4cd28099..00000000 --- a/pkcs8/tests/encrypted_private_key.rs +++ /dev/null @@ -1,229 +0,0 @@ -//! Encrypted PKCS#8 private key tests. - -#![cfg(feature = "pkcs5")] - -use core::convert::TryFrom; -use hex_literal::hex; -use pkcs8::{pkcs5::pbes2, EncryptedPrivateKeyInfo}; - -#[cfg(feature = "encryption")] -use pkcs8::PrivateKeyDocument; - -#[cfg(feature = "pem")] -use pkcs8::EncryptedPrivateKeyDocument; - -/// Ed25519 PKCS#8 private key plaintext encoded as ASN.1 DER -#[cfg(feature = "encryption")] -const ED25519_DER_PLAINTEXT_EXAMPLE: &[u8] = include_bytes!("examples/ed25519-priv-pkcs8v1.der"); - -/// Ed25519 PKCS#8 encrypted private key (PBES2 + AES-128-CBC + PBKDF2-SHA1) encoded as ASN.1 DER. -/// -/// Generated using: -/// -/// ``` -/// $ openssl pkcs8 -v2 aes256-cbc -v2prf hmacWithSHA1 -topk8 -inform der -in ed25519-priv.der -outform der -out ed25519-encpriv-aes128-pbkdf2-sha1.der -/// ``` -const ED25519_DER_AES128_PBKDF2_SHA1_EXAMPLE: &[u8] = - include_bytes!("examples/ed25519-encpriv-aes128-pbkdf2-sha1.der"); - -/// Ed25519 PKCS#8 encrypted private key (PBES2 + AES-256-CBC + PBKDF2-SHA256) encoded as ASN.1 DER. -/// -/// Generated using: -/// -/// ``` -/// $ openssl pkcs8 -v2 aes256-cbc -v2prf hmacWithSHA256 -topk8 -inform der -in ed25519-priv.der -outform der -out ed25519-encpriv-aes256-pbkdf2-sha256.der -/// ``` -const ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE: &[u8] = - include_bytes!("examples/ed25519-encpriv-aes256-pbkdf2-sha256.der"); - -/// Ed25519 PKCS#8 encrypted private key (PBES2 + AES-256-CBC + scrypt) encoded as ASN.1 DER. -/// -/// Generated using: -/// -/// ``` -/// $ openssl pkcs8 -v2 aes256-cbc -scrypt -topk8 -inform der -in ed25519-priv.der -outform der -out ed25519-encpriv-aes256-scrypt.der -/// ``` -#[cfg(feature = "encryption")] -const ED25519_DER_AES256_SCRYPT_EXAMPLE: &[u8] = - include_bytes!("examples/ed25519-encpriv-aes256-scrypt.der"); - -/// Ed25519 PKCS#8 encrypted private key encoded as PEM -#[cfg(feature = "pem")] -const ED25519_PEM_AES256_PBKDF2_SHA256_EXAMPLE: &str = - include_str!("examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem"); - -/// Password used to encrypt the keys. -#[cfg(feature = "encryption")] -const PASSWORD: &[u8] = b"hunter42"; // Bad password; don't actually use outside tests! - -#[test] -fn decode_ed25519_encpriv_aes128_pbkdf2_sha1_der() { - let pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES128_PBKDF2_SHA1_EXAMPLE).unwrap(); - - assert_eq!( - pk.encryption_algorithm.oid(), - "1.2.840.113549.1.5.13".parse().unwrap() - ); // PBES2 - - let pbes2_params = pk.encryption_algorithm.pbes2().unwrap(); - let pbkdf2_params = pbes2_params.kdf.pbkdf2().unwrap(); - - assert_eq!(pbkdf2_params.salt, hex!("e8765e01e43b6bad")); - assert_eq!(pbkdf2_params.iteration_count, 2048); - assert_eq!(pbkdf2_params.key_length, None); - assert_eq!(pbkdf2_params.prf, pbes2::Pbkdf2Prf::HmacWithSha1); - - match pbes2_params.encryption { - pbes2::EncryptionScheme::Aes128Cbc { iv } => { - assert_eq!(iv, &hex!("223080a71bcd2b9a256d876c924979d2")); - } - other => panic!("unexpected encryption scheme: {:?}", other), - } - - // Extracted with: - // $ openssl asn1parse -inform der -in tests/examples/ed25519-encpriv-aes128-sha1.der - assert_eq!( - pk.encrypted_data, - &hex!("4B4D091548EAC381EE7663B21234CD4FF3C9DF664D713394CACCEA7C9B982BD8F29910FABCA4BF7BE0431FAC5C4D657BE997C1F5BF40E2DA465AC1FCC2E30470") - ); -} - -#[test] -fn decode_ed25519_encpriv_aes256_pbkdf2_sha256_der() { - let pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE).unwrap(); - - assert_eq!( - pk.encryption_algorithm.oid(), - "1.2.840.113549.1.5.13".parse().unwrap() - ); // PBES2 - - let pbes2_params = pk.encryption_algorithm.pbes2().unwrap(); - let pbkdf2_params = pbes2_params.kdf.pbkdf2().unwrap(); - - assert_eq!(pbkdf2_params.salt, hex!("79d982e70df91a88")); - assert_eq!(pbkdf2_params.iteration_count, 2048); - assert_eq!(pbkdf2_params.key_length, None); - assert_eq!(pbkdf2_params.prf, pbes2::Pbkdf2Prf::HmacWithSha256); - - match pbes2_params.encryption { - pbes2::EncryptionScheme::Aes256Cbc { iv } => { - assert_eq!(iv, &hex!("b2d02d78b2efd9dff694cf8e0af40925")); - } - other => panic!("unexpected encryption scheme: {:?}", other), - } - - // Extracted with: - // $ openssl asn1parse -inform der -in tests/examples/ed25519-encpriv-aes256-sha256.der - assert_eq!( - pk.encrypted_data, - &hex!("D0CD6C770F4BB87176422305C17401809E226674CE74185D221BFDAA95069890C8882FCE02B05D41BCBF54B035595BCD4154B32593708469B86AACF8815A7B2B") - ); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_ed25519_encpriv_aes256_pbkdf2_sha256_pem() { - let pkcs8_doc: EncryptedPrivateKeyDocument = - ED25519_PEM_AES256_PBKDF2_SHA256_EXAMPLE.parse().unwrap(); - - assert_eq!(pkcs8_doc.as_ref(), ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE); - - // Ensure `EncryptedPrivateKeyDocument` parses successfully - assert_eq!( - pkcs8_doc.encrypted_private_key_info(), - EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE).unwrap() - ); -} - -#[cfg(feature = "encryption")] -#[test] -fn decrypt_ed25519_der_encpriv_aes256_pbkdf2_sha256() { - let enc_pk = - EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE).unwrap(); - let pk = enc_pk.decrypt(PASSWORD).unwrap(); - assert_eq!(pk.as_ref(), ED25519_DER_PLAINTEXT_EXAMPLE); -} - -#[cfg(feature = "encryption")] -#[test] -fn decrypt_ed25519_der_encpriv_aes256_scrypt() { - let enc_pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_SCRYPT_EXAMPLE).unwrap(); - let pk = enc_pk.decrypt(PASSWORD).unwrap(); - assert_eq!(pk.as_ref(), ED25519_DER_PLAINTEXT_EXAMPLE); -} - -#[cfg(feature = "encryption")] -#[test] -fn encrypt_ed25519_der_encpriv_aes256_pbkdf2_sha256() { - let pbes2_params = pkcs5::pbes2::Parameters::pbkdf2_sha256_aes256cbc( - 2048, - &hex!("79d982e70df91a88"), - &hex!("b2d02d78b2efd9dff694cf8e0af40925"), - ) - .unwrap(); - - let pk_plaintext = PrivateKeyDocument::try_from(ED25519_DER_PLAINTEXT_EXAMPLE).unwrap(); - let pk_encrypted = pk_plaintext - .encrypt_with_params(pbes2_params, PASSWORD) - .unwrap(); - - assert_eq!( - pk_encrypted.as_ref(), - ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE - ); -} - -#[cfg(feature = "encryption")] -#[test] -fn encrypt_ed25519_der_encpriv_aes256_scrypt() { - let scrypt_params = pkcs5::pbes2::Parameters::scrypt_aes256cbc( - Default::default(), - &hex!("E6211E2348AD69E0"), - &hex!("9BD0A6251F2254F9FD5963887C27CF01"), - ) - .unwrap(); - - let pk_plaintext = PrivateKeyDocument::try_from(ED25519_DER_PLAINTEXT_EXAMPLE).unwrap(); - let pk_encrypted = pk_plaintext - .encrypt_with_params(scrypt_params, PASSWORD) - .unwrap(); - - assert_eq!(pk_encrypted.as_ref(), ED25519_DER_AES256_SCRYPT_EXAMPLE); -} - -#[test] -#[cfg(feature = "alloc")] -fn encode_ed25519_encpriv_aes256_pbkdf2_sha256_der() { - let pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE).unwrap(); - assert_eq!( - ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE, - pk.to_der().as_ref() - ); -} - -#[test] -#[cfg(feature = "pem")] -fn encode_ed25519_encpriv_aes256_pbkdf2_sha256_pem() { - let pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE).unwrap(); - assert_eq!(ED25519_PEM_AES256_PBKDF2_SHA256_EXAMPLE, &*pk.to_pem()); -} - -#[test] -#[cfg(feature = "std")] -fn read_der_file() { - let pkcs8_doc = EncryptedPrivateKeyDocument::read_der_file( - "tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der", - ) - .unwrap(); - assert_eq!(pkcs8_doc.as_ref(), ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE); -} - -#[test] -#[cfg(all(feature = "pem", feature = "std"))] -fn read_pem_file() { - let pkcs8_doc = EncryptedPrivateKeyDocument::read_pem_file( - "tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem", - ) - .unwrap(); - assert_eq!(pkcs8_doc.as_ref(), ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE); -} diff --git a/pkcs8/tests/examples/ed25519-encpriv-aes128-pbkdf2-sha1.der b/pkcs8/tests/examples/ed25519-encpriv-aes128-pbkdf2-sha1.der deleted file mode 100644 index c8d6edf7..00000000 Binary files a/pkcs8/tests/examples/ed25519-encpriv-aes128-pbkdf2-sha1.der and /dev/null differ diff --git a/pkcs8/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der b/pkcs8/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der deleted file mode 100644 index 5170c06e..00000000 Binary files a/pkcs8/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der and /dev/null differ diff --git a/pkcs8/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem b/pkcs8/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem deleted file mode 100644 index e5d3207a..00000000 --- a/pkcs8/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAh52YLnDfkaiAICCAAw -DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEELLQLXiy79nf9pTPjgr0CSUEQNDN -bHcPS7hxdkIjBcF0AYCeImZ0znQYXSIb/aqVBpiQyIgvzgKwXUG8v1SwNVlbzUFU -syWTcIRpuGqs+IFaeys= ------END ENCRYPTED PRIVATE KEY----- diff --git a/pkcs8/tests/examples/ed25519-encpriv-aes256-scrypt.der b/pkcs8/tests/examples/ed25519-encpriv-aes256-scrypt.der deleted file mode 100644 index a045982f..00000000 Binary files a/pkcs8/tests/examples/ed25519-encpriv-aes256-scrypt.der and /dev/null differ diff --git a/pkcs8/tests/examples/ed25519-encpriv-aes256-scrypt.pem b/pkcs8/tests/examples/ed25519-encpriv-aes256-scrypt.pem deleted file mode 100644 index 1f0562d8..00000000 --- a/pkcs8/tests/examples/ed25519-encpriv-aes256-scrypt.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjmIR4jSK1p4AICQAAC -AQgCAQEwHQYJYIZIAWUDBAEqBBCb0KYlHyJU+f1ZY4h8J88BBEDMYrp3PA9JX6s2 -aOT8782wjnig7hXgoVAT9iq+CNqnQgZe6zZtbmyYzDsOfmm9yGHIiv648D26Hixt -mdBtFzYM ------END ENCRYPTED PRIVATE KEY----- diff --git a/pkcs8/tests/examples/ed25519-priv-pkcs8v1.der b/pkcs8/tests/examples/ed25519-priv-pkcs8v1.der deleted file mode 100644 index 0cfccc39..00000000 Binary files a/pkcs8/tests/examples/ed25519-priv-pkcs8v1.der and /dev/null differ diff --git a/pkcs8/tests/examples/ed25519-priv-pkcs8v1.pem b/pkcs8/tests/examples/ed25519-priv-pkcs8v1.pem deleted file mode 100644 index 0c0ee10b..00000000 --- a/pkcs8/tests/examples/ed25519-priv-pkcs8v1.pem +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIBftnHPp22SewYmmEoMcX8VwI4IHwaqd+9LFPj/15eqF ------END PRIVATE KEY----- diff --git a/pkcs8/tests/examples/ed25519-priv-pkcs8v2.der b/pkcs8/tests/examples/ed25519-priv-pkcs8v2.der deleted file mode 100644 index 6e0ca99c..00000000 Binary files a/pkcs8/tests/examples/ed25519-priv-pkcs8v2.der and /dev/null differ diff --git a/pkcs8/tests/examples/ed25519-pub.der b/pkcs8/tests/examples/ed25519-pub.der deleted file mode 100644 index 1b602ee1..00000000 Binary files a/pkcs8/tests/examples/ed25519-pub.der and /dev/null differ diff --git a/pkcs8/tests/examples/ed25519-pub.pem b/pkcs8/tests/examples/ed25519-pub.pem deleted file mode 100644 index 6891701f..00000000 --- a/pkcs8/tests/examples/ed25519-pub.pem +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEATSkWfz8ZEqb3rfopOgUaFcBexnuPFyZ7HFVQ3OhTvQ0= ------END PUBLIC KEY----- diff --git a/pkcs8/tests/examples/p256-priv.der b/pkcs8/tests/examples/p256-priv.der deleted file mode 100644 index c0de45ef..00000000 Binary files a/pkcs8/tests/examples/p256-priv.der and /dev/null differ diff --git a/pkcs8/tests/examples/p256-priv.pem b/pkcs8/tests/examples/p256-priv.pem deleted file mode 100644 index 09b9343c..00000000 --- a/pkcs8/tests/examples/p256-priv.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgaWJBcVYaYzQN4OfY -afKgVJJVjhoEhotqn4VKhmeIGI2hRANCAAQcrP+1Xy8s79idies3SyaBFSRSgC3u -oJkWBoE32DnPf8SBpESSME1+9mrBF77+g6jQjxVfK1L59hjdRHApBI4P ------END PRIVATE KEY----- diff --git a/pkcs8/tests/examples/p256-pub.der b/pkcs8/tests/examples/p256-pub.der deleted file mode 100644 index 67c719c7..00000000 Binary files a/pkcs8/tests/examples/p256-pub.der and /dev/null differ diff --git a/pkcs8/tests/examples/p256-pub.pem b/pkcs8/tests/examples/p256-pub.pem deleted file mode 100644 index ee7e5b61..00000000 --- a/pkcs8/tests/examples/p256-pub.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHKz/tV8vLO/YnYnrN0smgRUkUoAt -7qCZFgaBN9g5z3/EgaREkjBNfvZqwRe+/oOo0I8VXytS+fYY3URwKQSODw== ------END PUBLIC KEY----- diff --git a/pkcs8/tests/examples/rsa2048-priv.der b/pkcs8/tests/examples/rsa2048-priv.der deleted file mode 100644 index f4590bbe..00000000 Binary files a/pkcs8/tests/examples/rsa2048-priv.der and /dev/null differ diff --git a/pkcs8/tests/examples/rsa2048-priv.pem b/pkcs8/tests/examples/rsa2048-priv.pem deleted file mode 100644 index e2a218c8..00000000 --- a/pkcs8/tests/examples/rsa2048-priv.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2xCxRXxCmqvKC -xj7b4kJDoXDz+iYzvUgzY39Hyk9vNuA6XSnvwxkayA85DYdLOeMPQU/Owfyg7YHl -R+3CzTgsdvYckBiXPbn6U3lyp8cB9rd+CYLfwV/AGSfuXnzZS09Zn/BwE6fIKBvf -Ity8mtfKu3xDEcmC9Y7bchOtRVizMiZtdDrtgZLRiEytuLFHOaja2mbclwgG2ces -RQyxPQ18V1+xmFNPxhvEG8DwV04OATDHu7+9/cn2puLj4q/xy+rIm6V4hFKNVc+w -gyeh6MifTgA88oiOkzJB2daVvLus3JC0Tj4JX6NwWOolsT9eKVy+rG3oOKuMUK9h -4piXW4cvAgMBAAECggEAfsyDYsDtsHQRZCFeIvdKudkboGkAcAz2NpDlEU2O5r3P -uy4/lhRpKmd6CD8Wil5S5ZaOZAe52XxuDkBk+C2gt1ihTxe5t9QfX0jijWVRcE9W -5p56qfpjD8dkKMBtJeRV3PxVt6wrT3ZkP97T/hX/eKuyfmWsxKrQvfbbJ+9gppEM -XEoIXtQydasZwdmXoyxu/8598tGTX25gHu3hYaErXMJ8oh+B0smcPR6gjpDjBTqw -m++nJN7w0MOjwel0DA2fdhJqFJ7Aqn2AeCBUhCVNlR2wfEz5H7ZFTAlliP1ZJNur -6zWcogJSaNAE+dZus9b3rcETm61A8W3eY54RZHN2wQKBgQDcwGEkLU6Sr67nKsUT -ymW593A2+b1+Dm5hRhp+92VCJewVPH5cMaYVem5aE/9uF46HWMHLM9nWu+MXnvGJ -mOQi7Ny+149Oz9vl9PzYrsLJ0NyGRzypvRbZ0jjSH7Xd776xQ8ph0L1qqNkfM6CX -eQ6WQNvJEIXcXyY0O6MTj2stZwKBgQDT8xR1fkDpVINvkr4kI2ry8NoEo0ZTwYCv -Z+lgCG2T/eZcsj79nQk3R2L1mB42GEmvaM3XU5T/ak4G62myCeQijbLfpw5A9/l1 -ClKBdmR7eI0OV3eiy4si480mf/cLTzsC06r7DhjFkKVksDGIsKpfxIFWsHYiIUJD -vRIn76fy+QKBgQDOaLesGw0QDWNuVUiHU8XAmEP9s5DicF33aJRXyb2Nl2XjCXhh -fi78gEj0wyQgbbhgh7ZU6Xuz1GTn7j+M2D/hBDb33xjpqWPE5kkR1n7eNAQvLibj -06GtNGra1rm39ncIywlOYt7p/01dZmmvmIryJV0c6O0xfGp9hpHaNU0S2wKBgCX2 -5ZRCIChrTfu/QjXA7lhD0hmAkYlRINbKeyALgm0+znOOLgBJj6wKKmypacfww8oa -sLxAKXEyvnU4177fTLDvxrmO99ulT1aqmaq85TTEnCeUfUZ4xRxjx4x84WhyMbTI -61h65u8EgMuvT8AXPP1Yen5nr1FfubnedREYOXIpAoGAMZlUBtQGIHyt6uo1s40E -DF+Kmhrggn6e0GsVPYO2ghk1tLNqgr6dVseRtYwnJxpXk9U6HWV8CJl5YLFDPlFx -mH9FLxRKfHIwbWPh0//Atxt1qwjy5FpILpiEUcvkeOEusijQdFbJJLZvbO0EjYU/ -Uz4xpoYU8cPObY7JmDznKvc= ------END PRIVATE KEY----- diff --git a/pkcs8/tests/examples/rsa2048-pub.der b/pkcs8/tests/examples/rsa2048-pub.der deleted file mode 100644 index 4148aaaa..00000000 Binary files a/pkcs8/tests/examples/rsa2048-pub.der and /dev/null differ diff --git a/pkcs8/tests/examples/rsa2048-pub.pem b/pkcs8/tests/examples/rsa2048-pub.pem deleted file mode 100644 index 5ecd8923..00000000 --- a/pkcs8/tests/examples/rsa2048-pub.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtsQsUV8QpqrygsY+2+JC -Q6Fw8/omM71IM2N/R8pPbzbgOl0p78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04 -LHb2HJAYlz25+lN5cqfHAfa3fgmC38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrX -yrt8QxHJgvWO23ITrUVYszImbXQ67YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0N -fFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejI -n04APPKIjpMyQdnWlby7rNyQtE4+CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uH -LwIDAQAB ------END PUBLIC KEY----- diff --git a/pkcs8/tests/private_key.rs b/pkcs8/tests/private_key.rs deleted file mode 100644 index 03687687..00000000 --- a/pkcs8/tests/private_key.rs +++ /dev/null @@ -1,188 +0,0 @@ -//! PKCS#8 private key tests - -use core::convert::TryFrom; -use hex_literal::hex; -use pkcs8::{PrivateKeyInfo, Version}; - -#[cfg(any(feature = "pem", feature = "std"))] -use pkcs8::PrivateKeyDocument; - -/// Elliptic Curve (P-256) PKCS#8 private key encoded as ASN.1 DER -const EC_P256_DER_EXAMPLE: &[u8] = include_bytes!("examples/p256-priv.der"); - -/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER -const ED25519_DER_V1_EXAMPLE: &[u8] = include_bytes!("examples/ed25519-priv-pkcs8v1.der"); - -/// Ed25519 PKCS#8 v2 private key + public key encoded as ASN.1 DER -const ED25519_DER_V2_EXAMPLE: &[u8] = include_bytes!("examples/ed25519-priv-pkcs8v2.der"); - -/// RSA-2048 PKCS#8 private key encoded as ASN.1 DER -const RSA_2048_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv.der"); - -/// Elliptic Curve (P-256) PKCS#8 private key encoded as PEM -#[cfg(feature = "pem")] -const EC_P256_PEM_EXAMPLE: &str = include_str!("examples/p256-priv.pem"); - -/// Ed25519 PKCS#8 private key encoded as PEM -#[cfg(feature = "pem")] -const ED25519_PEM_V1_EXAMPLE: &str = include_str!("examples/ed25519-priv-pkcs8v1.pem"); - -/// RSA-2048 PKCS#8 private key encoded as PEM -#[cfg(feature = "pem")] -const RSA_2048_PEM_EXAMPLE: &str = include_str!("examples/rsa2048-priv.pem"); - -#[test] -fn decode_ec_p256_der() { - let pk = PrivateKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap(); - - assert_eq!(pk.version(), Version::V1); - assert_eq!(pk.algorithm.oid, "1.2.840.10045.2.1".parse().unwrap()); - - assert_eq!( - pk.algorithm.parameters.unwrap().oid().unwrap(), - "1.2.840.10045.3.1.7".parse().unwrap() - ); - - // Extracted with: - // $ openssl asn1parse -inform der -in tests/examples/p256-priv.der - assert_eq!(pk.private_key, &hex!("306B020101042069624171561A63340DE0E7D869F2A05492558E1A04868B6A9F854A866788188DA144034200041CACFFB55F2F2CEFD89D89EB374B2681152452802DEEA09916068137D839CF7FC481A44492304D7EF66AC117BEFE83A8D08F155F2B52F9F618DD447029048E0F")[..]); -} - -#[test] -fn decode_ed25519_der_v1() { - let pk = PrivateKeyInfo::try_from(ED25519_DER_V1_EXAMPLE).unwrap(); - assert_eq!(pk.version(), Version::V1); - assert_eq!(pk.algorithm.oid, "1.3.101.112".parse().unwrap()); - assert_eq!(pk.algorithm.parameters, None); - - // Extracted with: - // $ openssl asn1parse -inform der -in tests/examples/ed25519-priv.der - assert_eq!( - pk.private_key, - &hex!("042017ED9C73E9DB649EC189A612831C5FC570238207C1AA9DFBD2C53E3FF5E5EA85")[..] - ); -} - -#[test] -fn decode_ed25519_der_v2() { - const PRIV_KEY: [u8; 34] = - hex!("04203A133DABADA2AA9CE54B0961CC3F1576B0943DC86EBF72A56E052C43F30FA3A5"); - const PUB_KEY: [u8; 32] = - hex!("A3A7EAE3A8373830BC47E1167BC50E1DB551999651E0E2DC587623438EAC3F31"); - - let pk = PrivateKeyInfo::try_from(ED25519_DER_V2_EXAMPLE).unwrap(); - assert_eq!(pk.version(), Version::V2); - assert_eq!(pk.algorithm.oid, "1.3.101.112".parse().unwrap()); - assert_eq!(pk.algorithm.parameters, None); - assert_eq!(pk.private_key, PRIV_KEY); - assert_eq!(pk.public_key, Some(&PUB_KEY[..])); -} - -#[test] -fn decode_rsa_2048_der() { - let pk = PrivateKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - assert_eq!(pk.version(), Version::V1); - assert_eq!(pk.algorithm.oid, "1.2.840.113549.1.1.1".parse().unwrap()); - assert!(pk.algorithm.parameters.unwrap().is_null()); - - // Extracted with: - // $ openssl asn1parse -inform der -in tests/examples/rsa2048-priv.der - assert_eq!(pk.private_key, &hex!("308204A30201000282010100B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEAC89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F0203010001028201007ECC8362C0EDB0741164215E22F74AB9D91BA06900700CF63690E5114D8EE6BDCFBB2E3F9614692A677A083F168A5E52E5968E6407B9D97C6E0E4064F82DA0B758A14F17B9B7D41F5F48E28D6551704F56E69E7AA9FA630FC76428C06D25E455DCFC55B7AC2B4F76643FDED3FE15FF78ABB27E65ACC4AAD0BDF6DB27EF60A6910C5C4A085ED43275AB19C1D997A32C6EFFCE7DF2D1935F6E601EEDE161A12B5CC27CA21F81D2C99C3D1EA08E90E3053AB09BEFA724DEF0D0C3A3C1E9740C0D9F76126A149EC0AA7D8078205484254D951DB07C4CF91FB6454C096588FD5924DBABEB359CA2025268D004F9D66EB3D6F7ADC1139BAD40F16DDE639E11647376C102818100DCC061242D4E92AFAEE72AC513CA65B9F77036F9BD7E0E6E61461A7EF7654225EC153C7E5C31A6157A6E5A13FF6E178E8758C1CB33D9D6BBE3179EF18998E422ECDCBED78F4ECFDBE5F4FCD8AEC2C9D0DC86473CA9BD16D9D238D21FB5DDEFBEB143CA61D0BD6AA8D91F33A097790E9640DBC91085DC5F26343BA3138F6B2D6702818100D3F314757E40E954836F92BE24236AF2F0DA04A34653C180AF67E960086D93FDE65CB23EFD9D09374762F5981E361849AF68CDD75394FF6A4E06EB69B209E4228DB2DFA70E40F7F9750A528176647B788D0E5777A2CB8B22E3CD267FF70B4F3B02D3AAFB0E18C590A564B03188B0AA5FC48156B07622214243BD1227EFA7F2F902818100CE68B7AC1B0D100D636E55488753C5C09843FDB390E2705DF7689457C9BD8D9765E30978617E2EFC8048F4C324206DB86087B654E97BB3D464E7EE3F8CD83FE10436F7DF18E9A963C4E64911D67EDE34042F2E26E3D3A1AD346ADAD6B9B7F67708CB094E62DEE9FF4D5D6669AF988AF2255D1CE8ED317C6A7D8691DA354D12DB02818025F6E5944220286B4DFBBF4235C0EE5843D2198091895120D6CA7B200B826D3ECE738E2E00498FAC0A2A6CA969C7F0C3CA1AB0BC40297132BE7538D7BEDF4CB0EFC6B98EF7DBA54F56AA99AABCE534C49C27947D4678C51C63C78C7CE1687231B4C8EB587AE6EF0480CBAF4FC0173CFD587A7E67AF515FB9B9DE75111839722902818031995406D406207CADEAEA35B38D040C5F8A9A1AE0827E9ED06B153D83B6821935B4B36A82BE9D56C791B58C27271A5793D53A1D657C08997960B1433E5171987F452F144A7C72306D63E1D3FFC0B71B75AB08F2E45A482E988451CBE478E12EB228D07456C924B66F6CED048D853F533E31A68614F1C3CE6D8EC9983CE72AF7")[..]); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_ec_p256_pem() { - let pkcs8_doc: PrivateKeyDocument = EC_P256_PEM_EXAMPLE.parse().unwrap(); - assert_eq!(pkcs8_doc.as_ref(), EC_P256_DER_EXAMPLE); - - // Ensure `PrivateKeyDocument` parses successfully - let pk_info = PrivateKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap(); - assert_eq!(pkcs8_doc.private_key_info().algorithm, pk_info.algorithm); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_ed25519_pem() { - let pkcs8_doc: PrivateKeyDocument = ED25519_PEM_V1_EXAMPLE.parse().unwrap(); - assert_eq!(pkcs8_doc.as_ref(), ED25519_DER_V1_EXAMPLE); - - // Ensure `PrivateKeyDocument` parses successfully - let pk_info = PrivateKeyInfo::try_from(ED25519_DER_V1_EXAMPLE).unwrap(); - assert_eq!(pkcs8_doc.private_key_info().algorithm, pk_info.algorithm); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_rsa_2048_pem() { - let pkcs8_doc: PrivateKeyDocument = RSA_2048_PEM_EXAMPLE.parse().unwrap(); - assert_eq!(pkcs8_doc.as_ref(), RSA_2048_DER_EXAMPLE); - - // Ensure `PrivateKeyDocument` parses successfully - let pk_info = PrivateKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - assert_eq!(pkcs8_doc.private_key_info().algorithm, pk_info.algorithm); -} - -#[test] -#[cfg(feature = "alloc")] -fn encode_ec_p256_der() { - let pk = PrivateKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap(); - let pk_encoded = pk.to_der(); - assert_eq!(EC_P256_DER_EXAMPLE, pk_encoded.as_ref()); -} - -#[test] -#[cfg(feature = "alloc")] -fn encode_ed25519_der_v1() { - let pk = PrivateKeyInfo::try_from(ED25519_DER_V1_EXAMPLE).unwrap(); - assert_eq!(ED25519_DER_V1_EXAMPLE, pk.to_der().as_ref()); -} - -#[test] -#[cfg(feature = "alloc")] -fn encode_ed25519_der_v2() { - let pk = PrivateKeyInfo::try_from(ED25519_DER_V2_EXAMPLE).unwrap(); - assert_eq!(ED25519_DER_V2_EXAMPLE, pk.to_der().as_ref()); -} - -#[test] -#[cfg(feature = "alloc")] -fn encode_rsa_2048_der() { - let pk = PrivateKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - assert_eq!(RSA_2048_DER_EXAMPLE, pk.to_der().as_ref()); -} - -#[test] -#[cfg(feature = "pem")] -fn encode_ec_p256_pem() { - let pk = PrivateKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap(); - assert_eq!(EC_P256_PEM_EXAMPLE, &*pk.to_pem()); -} - -#[test] -#[cfg(feature = "pem")] -fn encode_ed25519_pem() { - let pk = PrivateKeyInfo::try_from(ED25519_DER_V1_EXAMPLE).unwrap(); - assert_eq!(ED25519_PEM_V1_EXAMPLE, &*pk.to_pem()); -} - -#[test] -#[cfg(feature = "pem")] -fn encode_rsa_2048_pem() { - let pk = PrivateKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - assert_eq!(RSA_2048_PEM_EXAMPLE, &*pk.to_pem()); -} - -#[test] -#[cfg(feature = "std")] -fn read_der_file() { - let pkcs8_doc = PrivateKeyDocument::read_der_file("tests/examples/p256-priv.der").unwrap(); - assert_eq!(pkcs8_doc.as_ref(), EC_P256_DER_EXAMPLE); -} - -#[test] -#[cfg(all(feature = "pem", feature = "std"))] -fn read_pem_file() { - let pkcs8_doc = PrivateKeyDocument::read_pem_file("tests/examples/p256-priv.pem").unwrap(); - assert_eq!(pkcs8_doc.as_ref(), EC_P256_DER_EXAMPLE); -} diff --git a/pkcs8/tests/public_key.rs b/pkcs8/tests/public_key.rs deleted file mode 100644 index add9d495..00000000 --- a/pkcs8/tests/public_key.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! Public key (`SubjectPublicKeyInfo`) tests - -use core::convert::TryFrom; -use hex_literal::hex; -use pkcs8::SubjectPublicKeyInfo; - -#[cfg(feature = "alloc")] -use der::Encodable; - -#[cfg(any(feature = "pem", feature = "std"))] -use pkcs8::PublicKeyDocument; - -/// Elliptic Curve (P-256) `SubjectPublicKeyInfo` encoded as ASN.1 DER -const EC_P256_DER_EXAMPLE: &[u8] = include_bytes!("examples/p256-pub.der"); - -/// Ed25519 `SubjectPublicKeyInfo` encoded as ASN.1 DER -const ED25519_DER_EXAMPLE: &[u8] = include_bytes!("examples/ed25519-pub.der"); - -/// RSA-2048 `SubjectPublicKeyInfo` encoded as ASN.1 DER -const RSA_2048_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-pub.der"); - -/// Elliptic Curve (P-256) public key encoded as PEM -#[cfg(feature = "pem")] -const EC_P256_PEM_EXAMPLE: &str = include_str!("examples/p256-pub.pem"); - -/// Ed25519 public key encoded as PEM -#[cfg(feature = "pem")] -const ED25519_PEM_EXAMPLE: &str = include_str!("examples/ed25519-pub.pem"); - -/// RSA-2048 PKCS#8 public key encoded as PEM -#[cfg(feature = "pem")] -const RSA_2048_PEM_EXAMPLE: &str = include_str!("examples/rsa2048-pub.pem"); - -#[test] -fn decode_ec_p256_der() { - let spki = SubjectPublicKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap(); - - assert_eq!(spki.algorithm.oid, "1.2.840.10045.2.1".parse().unwrap()); - - assert_eq!( - spki.algorithm.parameters.unwrap().oid().unwrap(), - "1.2.840.10045.3.1.7".parse().unwrap() - ); - - assert_eq!(spki.subject_public_key, &hex!("041CACFFB55F2F2CEFD89D89EB374B2681152452802DEEA09916068137D839CF7FC481A44492304D7EF66AC117BEFE83A8D08F155F2B52F9F618DD447029048E0F")[..]); -} - -#[test] -fn decode_ed25519_der() { - let spki = SubjectPublicKeyInfo::try_from(ED25519_DER_EXAMPLE).unwrap(); - - assert_eq!(spki.algorithm.oid, "1.3.101.112".parse().unwrap()); - assert_eq!(spki.algorithm.parameters, None); - assert_eq!( - spki.subject_public_key, - &hex!("4D29167F3F1912A6F7ADFA293A051A15C05EC67B8F17267B1C5550DCE853BD0D")[..] - ); -} - -#[test] -fn decode_rsa_2048_der() { - let spki = SubjectPublicKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - - assert_eq!(spki.algorithm.oid, "1.2.840.113549.1.1.1".parse().unwrap()); - assert!(spki.algorithm.parameters.unwrap().is_null()); - assert_eq!(spki.subject_public_key, &hex!("3082010A0282010100B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEAC89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F0203010001")[..]); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_ec_p256_pem() { - let doc: PublicKeyDocument = EC_P256_PEM_EXAMPLE.parse().unwrap(); - assert_eq!(doc.as_ref(), EC_P256_DER_EXAMPLE); - - // Ensure `PublicKeyDocument` parses successfully - let spki = SubjectPublicKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap(); - assert_eq!(doc.spki(), spki); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_ed25519_pem() { - let doc: PublicKeyDocument = ED25519_PEM_EXAMPLE.parse().unwrap(); - assert_eq!(doc.as_ref(), ED25519_DER_EXAMPLE); - - // Ensure `PublicKeyDocument` parses successfully - let spki = SubjectPublicKeyInfo::try_from(ED25519_DER_EXAMPLE).unwrap(); - assert_eq!(doc.spki(), spki); -} - -#[test] -#[cfg(feature = "pem")] -fn decode_rsa_2048_pem() { - let doc: PublicKeyDocument = RSA_2048_PEM_EXAMPLE.parse().unwrap(); - assert_eq!(doc.as_ref(), RSA_2048_DER_EXAMPLE); - - // Ensure `PublicKeyDocument` parses successfully - let spki = SubjectPublicKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - assert_eq!(doc.spki(), spki); -} - -#[test] -#[cfg(feature = "alloc")] -fn encode_ec_p256_der() { - let pk = SubjectPublicKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap(); - let pk_encoded = pk.to_vec().unwrap(); - assert_eq!(EC_P256_DER_EXAMPLE, pk_encoded.as_slice()); -} - -#[test] -#[cfg(feature = "alloc")] -fn encode_ed25519_der() { - let pk = SubjectPublicKeyInfo::try_from(ED25519_DER_EXAMPLE).unwrap(); - let pk_encoded = pk.to_vec().unwrap(); - assert_eq!(ED25519_DER_EXAMPLE, pk_encoded.as_slice()); -} - -#[test] -#[cfg(feature = "alloc")] -fn encode_rsa_2048_der() { - let pk = SubjectPublicKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - let pk_encoded = pk.to_vec().unwrap(); - assert_eq!(RSA_2048_DER_EXAMPLE, pk_encoded.as_slice()); -} - -#[test] -#[cfg(feature = "pem")] -fn encode_ec_p256_pem() { - let pk = SubjectPublicKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap(); - let pk_encoded = PublicKeyDocument::from(pk).to_pem(); - assert_eq!(EC_P256_PEM_EXAMPLE, pk_encoded); -} - -#[test] -#[cfg(feature = "pem")] -fn encode_ed25519_pem() { - let pk = SubjectPublicKeyInfo::try_from(ED25519_DER_EXAMPLE).unwrap(); - let pk_encoded = PublicKeyDocument::from(pk).to_pem(); - assert_eq!(ED25519_PEM_EXAMPLE, pk_encoded); -} - -#[test] -#[cfg(feature = "pem")] -fn encode_rsa_2048_pem() { - let pk = SubjectPublicKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap(); - let pk_encoded = PublicKeyDocument::from(pk).to_pem(); - assert_eq!(RSA_2048_PEM_EXAMPLE, pk_encoded); -} - -#[test] -#[cfg(feature = "std")] -fn read_der_file() { - let pkcs8_doc = PublicKeyDocument::read_der_file("tests/examples/p256-pub.der").unwrap(); - assert_eq!(pkcs8_doc.as_ref(), EC_P256_DER_EXAMPLE); -} - -#[test] -#[cfg(all(feature = "pem", feature = "std"))] -fn read_pem_file() { - let pkcs8_doc = PublicKeyDocument::read_pem_file("tests/examples/p256-pub.pem").unwrap(); - assert_eq!(pkcs8_doc.as_ref(), EC_P256_DER_EXAMPLE); -} diff --git a/spki/CHANGELOG.md b/spki/CHANGELOG.md deleted file mode 100644 index 97a5cf54..00000000 --- a/spki/CHANGELOG.md +++ /dev/null @@ -1,41 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.4.0 (2021-06-07) -### Added -- `AlgorithmIdentifier::assert_oids` ([#485], [#486]) - -### Changed -- Bump `der` to v0.4 ([#490]) - -[#485]: https://github.com/RustCrypto/utils/pull/485 -[#486]: https://github.com/RustCrypto/utils/pull/486 -[#490]: https://github.com/RustCrypto/utils/pull/490 - -## 0.3.0 (2021-03-22) -### Changed -- Bump `der` to v0.3 ([#354]) - -### Removed -- `AlgorithmParameters` enum ([#343]) - -[#343]: https://github.com/RustCrypto/utils/pull/343 -[#354]: https://github.com/RustCrypto/utils/pull/354 - -## 0.2.1 (2021-02-22) -### Added -- Impl `Choice` for `AlgorithmParameters` ([#295]) - -[#295]: https://github.com/RustCrypto/utils/pull/295 - -## 0.2.0 (2021-02-18) -### Changed -- Return `Result` from `AlgorithmIdentifier::params_*` ([#274]) - -[#274]: https://github.com/RustCrypto/utils/pull/274 - -## 0.1.0 (2021-02-16) -- Initial release diff --git a/spki/Cargo.toml b/spki/Cargo.toml deleted file mode 100644 index 1ccb0a16..00000000 --- a/spki/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "spki" -version = "0.4.0" # Also update html_root_url in lib.rs when bumping this -description = """ -X.509 Subject Public Key Info (RFC5280) describing public keys as well as their -associated AlgorithmIdentifiers (i.e. OIDs) -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -repository = "https://github.com/RustCrypto/utils/tree/master/spki" -categories = ["cryptography", "data-structures", "encoding", "no-std"] -keywords = ["crypto", "x509"] -readme = "README.md" - -[dependencies] -der = { version = "0.4", features = ["oid"], path = "../der" } - -[features] -std = ["der/std"] diff --git a/spki/LICENSE-APACHE b/spki/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/spki/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/spki/LICENSE-MIT b/spki/LICENSE-MIT deleted file mode 100644 index c869ada5..00000000 --- a/spki/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2021 The RustCrypto Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/spki/README.md b/spki/README.md deleted file mode 100644 index 31ecb88c..00000000 --- a/spki/README.md +++ /dev/null @@ -1,47 +0,0 @@ -# RustCrypto: X.509 Subject Public Key Info (SPKI) - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -X.509 Subject Public Key Info types describing public keys as well as their -associated AlgorithmIdentifiers (i.e. OIDs). - -Specified in [RFC 5280] (Section 4.1). - -[Documentation][docs-link] - -## License - -Licensed under either of: - - * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - * [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/spki.svg -[crate-link]: https://crates.io/crates/spki -[docs-image]: https://docs.rs/spki/badge.svg -[docs-link]: https://docs.rs/spki/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/spki/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions?query=workflow:spki - -[//]: # (general links) - -[RFC 5280]: https://tools.ietf.org/html/rfc5280#section-4.1 diff --git a/spki/src/algorithm.rs b/spki/src/algorithm.rs deleted file mode 100644 index 65b8b131..00000000 --- a/spki/src/algorithm.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! X.509 `AlgorithmIdentifier` - -use core::convert::{TryFrom, TryInto}; -use der::{ - asn1::{Any, ObjectIdentifier}, - Decodable, Encodable, Error, ErrorKind, Message, Result, -}; - -/// X.509 `AlgorithmIdentifier` as defined in [RFC 5280 Section 4.1.1.2]. -/// -/// ```text -/// AlgorithmIdentifier ::= SEQUENCE { -/// algorithm OBJECT IDENTIFIER, -/// parameters ANY DEFINED BY algorithm OPTIONAL } -/// ``` -/// -/// [RFC 5280 Section 4.1.1.2]: https://tools.ietf.org/html/rfc5280#section-4.1.1.2 -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct AlgorithmIdentifier<'a> { - /// Algorithm OID, i.e. the `algorithm` field in the `AlgorithmIdentifier` - /// ASN.1 schema. - pub oid: ObjectIdentifier, - - /// Algorithm `parameters`. - pub parameters: Option>, -} - -impl<'a> AlgorithmIdentifier<'a> { - /// Assert the `algorithm` OID is an expected value. - pub fn assert_algorithm_oid(&self, expected_oid: ObjectIdentifier) -> Result { - if self.oid == expected_oid { - Ok(expected_oid) - } else { - Err(ErrorKind::UnknownOid { oid: expected_oid }.into()) - } - } - - /// Assert `parameters` is an OID and has the expected value. - pub fn assert_parameters_oid( - &self, - expected_oid: ObjectIdentifier, - ) -> Result { - let actual_oid = self.parameters_oid()?; - - if actual_oid == expected_oid { - Ok(actual_oid) - } else { - Err(ErrorKind::UnknownOid { oid: expected_oid }.into()) - } - } - - /// Assert the values of the `algorithm` and `parameters` OIDs. - pub fn assert_oids( - &self, - algorithm: ObjectIdentifier, - parameters: ObjectIdentifier, - ) -> Result<()> { - self.assert_algorithm_oid(algorithm)?; - self.assert_parameters_oid(parameters)?; - Ok(()) - } - - /// Get the `parameters` field as an [`Any`]. - /// - /// Returns an error if `parameters` are `None`. - pub fn parameters_any(&self) -> Result> { - self.parameters.ok_or_else(|| ErrorKind::Truncated.into()) - } - - /// Get the `parameters` field as an [`ObjectIdentifier`]. - /// - /// Returns an error if it is absent or not an OID. - pub fn parameters_oid(&self) -> Result { - self.parameters_any().and_then(TryInto::try_into) - } -} - -impl<'a> TryFrom<&'a [u8]> for AlgorithmIdentifier<'a> { - type Error = Error; - - fn try_from(bytes: &'a [u8]) -> Result { - Self::from_der(bytes) - } -} - -impl<'a> TryFrom> for AlgorithmIdentifier<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - any.sequence(|decoder| { - let oid = decoder.decode()?; - let parameters = decoder.decode()?; - Ok(Self { oid, parameters }) - }) - } -} - -impl<'a> Message<'a> for AlgorithmIdentifier<'a> { - fn fields(&self, f: F) -> Result - where - F: FnOnce(&[&dyn Encodable]) -> Result, - { - f(&[&self.oid, &self.parameters]) - } -} diff --git a/spki/src/lib.rs b/spki/src/lib.rs deleted file mode 100644 index 25a252b8..00000000 --- a/spki/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! [X.509] Subject Public Key Info (SPKI) types describing public keys and their -//! associated [`AlgorithmIdentifier`] OIDs. -//! -//! Described in [RFC 5280 Section 4.1]. -//! -//! # Minimum Supported Rust Version -//! -//! This crate requires **Rust 1.51** at a minimum. -//! -//! # Usage -//! -//! The following example demonstrates how to use an OID as the `parameters` -//! of an [`AlgorithmIdentifier`]. -//! -//! Borrow the [`ObjectIdentifier`] first then use [`Into`] (or `Any::from`): -//! -//! ``` -//! use spki::{AlgorithmIdentifier, ObjectIdentifier}; -//! -//! let alg_oid = "1.2.840.10045.2.1".parse::().unwrap(); -//! let params_oid = "1.2.840.10045.3.1.7".parse::().unwrap(); -//! -//! let alg_id = AlgorithmIdentifier { -//! oid: alg_oid, -//! parameters: Some((¶ms_oid).into()) -//! }; -//! ``` -//! -//! [X.509]: https://en.wikipedia.org/wiki/X.509 -//! [RFC 5280 Section 4.1]: https://tools.ietf.org/html/rfc5280#section-4.1 - -#![no_std] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/spki/0.4.0" -)] -#![forbid(unsafe_code, clippy::unwrap_used)] -#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] - -mod algorithm; -mod spki; - -pub use crate::{algorithm::AlgorithmIdentifier, spki::SubjectPublicKeyInfo}; -pub use der::{self, asn1::ObjectIdentifier}; diff --git a/spki/src/spki.rs b/spki/src/spki.rs deleted file mode 100644 index 93db7e16..00000000 --- a/spki/src/spki.rs +++ /dev/null @@ -1,59 +0,0 @@ -//! X.509 `SubjectPublicKeyInfo` - -use crate::AlgorithmIdentifier; -use core::convert::TryFrom; -use der::{ - asn1::{Any, BitString}, - Decodable, Encodable, Error, Message, Result, -}; - -/// X.509 `SubjectPublicKeyInfo` (SPKI) as defined in [RFC 5280 Section 4.1.2.7]. -/// -/// ASN.1 structure containing an [`AlgorithmIdentifier`] and public key -/// data in an algorithm specific format. -/// -/// ```text -/// SubjectPublicKeyInfo ::= SEQUENCE { -/// algorithm AlgorithmIdentifier, -/// subjectPublicKey BIT STRING } -/// ``` -/// -/// [RFC 5280 Section 4.1.2.7]: https://tools.ietf.org/html/rfc5280#section-4.1.2.7 -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct SubjectPublicKeyInfo<'a> { - /// X.509 [`AlgorithmIdentifier`] for the public key type - pub algorithm: AlgorithmIdentifier<'a>, - - /// Public key data - pub subject_public_key: &'a [u8], -} - -impl<'a> TryFrom<&'a [u8]> for SubjectPublicKeyInfo<'a> { - type Error = Error; - - fn try_from(bytes: &'a [u8]) -> Result { - Self::from_der(bytes) - } -} - -impl<'a> TryFrom> for SubjectPublicKeyInfo<'a> { - type Error = Error; - - fn try_from(any: Any<'a>) -> Result> { - any.sequence(|decoder| { - Ok(Self { - algorithm: decoder.decode()?, - subject_public_key: decoder.bit_string()?.as_bytes(), - }) - }) - } -} - -impl<'a> Message<'a> for SubjectPublicKeyInfo<'a> { - fn fields(&self, f: F) -> Result - where - F: FnOnce(&[&dyn Encodable]) -> Result, - { - f(&[&self.algorithm, &BitString::new(self.subject_public_key)?]) - } -} diff --git a/x509/CHANGELOG.md b/wycheproof2blb/CHANGELOG.md similarity index 85% rename from x509/CHANGELOG.md rename to wycheproof2blb/CHANGELOG.md index d6637e04..2f7898a8 100644 --- a/x509/CHANGELOG.md +++ b/wycheproof2blb/CHANGELOG.md @@ -3,3 +3,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.1.0 (2021-12-07) +- Initial release diff --git a/wycheproof2blb/Cargo.toml b/wycheproof2blb/Cargo.toml index cb3a07a7..5bd025b3 100644 --- a/wycheproof2blb/Cargo.toml +++ b/wycheproof2blb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wycheproof2blb" -version = "0.1.0" +version = "0.1.0" # Also update html_root_url in lib.rs when bumping this authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" description = "A utility for converting Wycheproof test vectors to the blobby format" @@ -10,7 +10,7 @@ categories = ["cryptography", "no-std"] edition = "2018" [dependencies] -blobby = { version = "*", path = "../blobby" } -hex = "*" -serde = { version = "*", features = ["derive"] } -serde_json = "*" +blobby = { version = "0.3.1", path = "../blobby" } +hex = "0.4" +serde = { version = "1.0.103", features = ["derive"] } +serde_json = "1" diff --git a/wycheproof2blb/src/aes_siv.rs b/wycheproof2blb/src/aes_siv.rs index e4d399d5..46b7232b 100644 --- a/wycheproof2blb/src/aes_siv.rs +++ b/wycheproof2blb/src/aes_siv.rs @@ -13,6 +13,7 @@ struct TestSuite { #[derive(Debug, Deserialize)] struct TestGroup { + #[allow(dead_code)] #[serde(flatten)] pub group: wycheproof::Group, #[serde(rename = "keySize")] diff --git a/wycheproof2blb/src/ecdsa.rs b/wycheproof2blb/src/ecdsa.rs index c9877d99..edb69089 100644 --- a/wycheproof2blb/src/ecdsa.rs +++ b/wycheproof2blb/src/ecdsa.rs @@ -13,10 +13,13 @@ struct TestSuite { #[derive(Debug, Deserialize)] struct TestGroup { + #[allow(dead_code)] #[serde(flatten)] pub group: wycheproof::Group, + #[allow(dead_code)] #[serde(rename = "keyDer")] pub key_der: String, + #[allow(dead_code)] #[serde(rename = "keyPem")] pub key_pem: String, pub sha: String, @@ -27,6 +30,7 @@ struct TestGroup { #[derive(Debug, Deserialize)] struct TestKey { curve: String, + #[allow(dead_code)] #[serde(rename = "type")] key_type: String, #[serde(with = "hex_string")] diff --git a/wycheproof2blb/src/ed25519.rs b/wycheproof2blb/src/ed25519.rs index 9ae1e235..89054f33 100644 --- a/wycheproof2blb/src/ed25519.rs +++ b/wycheproof2blb/src/ed25519.rs @@ -13,10 +13,13 @@ struct TestSuite { #[derive(Debug, Deserialize)] struct TestGroup { + #[allow(dead_code)] #[serde(flatten)] pub group: wycheproof::Group, + #[allow(dead_code)] #[serde(rename = "keyDer")] pub key_der: String, + #[allow(dead_code)] #[serde(rename = "keyPem")] pub key_pem: String, pub key: TestKey, diff --git a/wycheproof2blb/src/hkdf.rs b/wycheproof2blb/src/hkdf.rs index 4901d92c..e449b99f 100644 --- a/wycheproof2blb/src/hkdf.rs +++ b/wycheproof2blb/src/hkdf.rs @@ -13,8 +13,10 @@ struct TestSuite { #[derive(Debug, Deserialize)] struct TestGroup { + #[allow(dead_code)] #[serde(flatten)] pub group: wycheproof::Group, + #[allow(dead_code)] #[serde(rename = "keySize")] pub key_size: u32, pub tests: Vec, diff --git a/wycheproof2blb/src/main.rs b/wycheproof2blb/src/main.rs index 889626ed..11082812 100644 --- a/wycheproof2blb/src/main.rs +++ b/wycheproof2blb/src/main.rs @@ -1,4 +1,9 @@ //! Tool to convert Wycheproof test vectors to raw hex format +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_root_url = "https://docs.rs/wycheproof2blb/0.1.0" +)] use std::io::Write; mod aead; @@ -131,7 +136,7 @@ fn main() { } let mut out_file = std::fs::File::create(out_path).unwrap(); - let blobs: Vec> = infos.into_iter().map(|info| info.data).flatten().collect(); + let blobs: Vec> = infos.into_iter().flat_map(|info| info.data).collect(); let (blb_data, _) = blobby::encode_blobs(&blobs); out_file.write_all(&blb_data).unwrap(); } diff --git a/x509/Cargo.toml b/x509/Cargo.toml deleted file mode 100644 index 804e3c1a..00000000 --- a/x509/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "x509" -version = "0.0.0" # Also update html_root_url in lib.rs when bumping this -description = """ -Pure Rust implementation of the X.509 Public Key Infrastructure Certificate -format as described in RFC 5280 -""" -authors = ["RustCrypto Developers"] -license = "Apache-2.0 OR MIT" -edition = "2018" -repository = "https://github.com/RustCrypto/utils/tree/master/x509" -categories = ["cryptography", "data-structures", "encoding", "no-std"] -keywords = ["crypto", "x.509"] -readme = "README.md" - -[dependencies] -der = { version = "0.4", features = ["derive"], path = "../der" } -spki = { version = "0.4", path = "../spki" } - -[features] -std = [] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] diff --git a/x509/LICENSE-APACHE b/x509/LICENSE-APACHE deleted file mode 100644 index 78173fa2..00000000 --- a/x509/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/x509/LICENSE-MIT b/x509/LICENSE-MIT deleted file mode 100644 index c869ada5..00000000 --- a/x509/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2021 The RustCrypto Project Developers - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/x509/README.md b/x509/README.md deleted file mode 100644 index ec3b9f68..00000000 --- a/x509/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# RustCrypto: X.509 - -[![crate][crate-image]][crate-link] -[![Docs][docs-image]][docs-link] -![Apache2/MIT licensed][license-image] -![Rust Version][rustc-image] -[![Project Chat][chat-image]][chat-link] -[![Build Status][build-image]][build-link] - -Pure Rust implementation of the X.509 Public Key Infrastructure Certificate -format as described in [RFC 5280]. - -[Documentation][docs-link] - -## Status - -tl;dr: not ready to use. - -This is a work-in-progress implementation which is at an early stage of -development. - -## License - -Licensed under either of: - -- [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) -- [MIT license](http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. - -[//]: # (badges) - -[crate-image]: https://img.shields.io/crates/v/x509.svg -[crate-link]: https://crates.io/crates/x509 -[docs-image]: https://docs.rs/x509/badge.svg -[docs-link]: https://docs.rs/x509/ -[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg -[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg -[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg -[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260052-utils -[build-image]: https://github.com/RustCrypto/utils/workflows/x509/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/utils/actions?query=workflow:x509 - -[//]: # (general links) - -[RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280 diff --git a/x509/src/attribute.rs b/x509/src/attribute.rs deleted file mode 100644 index 0656d9db..00000000 --- a/x509/src/attribute.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! X.509 Attributes - -use der::{ - asn1::{Any, ObjectIdentifier}, - Message, -}; - -/// Attribute type/value pairs as defined in [RFC 5280 Section 4.1.2.4]. -/// -/// ```text -/// AttributeTypeAndValue ::= SEQUENCE { -/// type AttributeType, -/// value AttributeValue } -/// -/// AttributeType ::= OBJECT IDENTIFIER -/// -/// AttributeValue ::= ANY -- DEFINED BY AttributeType -/// ``` -/// -/// [RFC 5280 Section 4.1.2.4]: https://tools.ietf.org/html/rfc5280#section-4.1.2.4 -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Message)] -pub struct AttributeTypeAndValue<'a> { - /// OID describing the type of the attribute - pub oid: ObjectIdentifier, - - /// Value of the attribute - pub value: Any<'a>, -} diff --git a/x509/src/lib.rs b/x509/src/lib.rs deleted file mode 100644 index 3cc14c81..00000000 --- a/x509/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Pure Rust implementation of the X.509 Public Key Infrastructure Certificate -//! format as described in [RFC 5280]. -//! -//! [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280 - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", - html_root_url = "https://docs.rs/x509/0.0.0" -)] -#![forbid(unsafe_code)] -#![warn(missing_docs, rust_2018_idioms)] - -extern crate alloc; - -#[cfg(feature = "std")] -extern crate std; - -mod attribute; -mod rdn; -mod time; - -pub use crate::{attribute::AttributeTypeAndValue, rdn::RelativeDistinguishedName, time::Time}; -pub use der::{self, asn1::ObjectIdentifier}; -pub use spki::{self, AlgorithmIdentifier, SubjectPublicKeyInfo}; - -use alloc::collections::BTreeSet as Set; diff --git a/x509/src/rdn.rs b/x509/src/rdn.rs deleted file mode 100644 index e758dd3b..00000000 --- a/x509/src/rdn.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Relative Distinguished Names - -use crate::{AttributeTypeAndValue, Set}; - -/// Relative Distinguished Name -#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] -pub struct RelativeDistinguishedName<'a>(Set>); diff --git a/x509/src/time.rs b/x509/src/time.rs deleted file mode 100644 index 6a3b1b19..00000000 --- a/x509/src/time.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Validity [`Time`] as defined in RFC 5280 - -use core::time::Duration; -use der::{ - asn1::{GeneralizedTime, UtcTime}, - Choice, -}; - -/// Validity [`Time`] as defined in [RFC 5280 Section 4.1.2.5]. -/// -/// Schema definition from [RFC 5280 Appendix A]: -/// -/// ```text -/// Time ::= CHOICE { -/// utcTime UTCTime, -/// generalTime GeneralizedTime } -/// ``` -/// -/// [RFC 5280 Section 4.1.2.5]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5 -/// [RFC 5280 Appendix A]: https://tools.ietf.org/html/rfc5280#page-117 -#[derive(Choice, Copy, Clone, Debug)] -pub enum Time { - /// Legacy UTC time (has 2-digit year, valid only through 2050). - #[asn1(type = "UTCTime")] - UtcTime(UtcTime), - - /// Modern [`GeneralizedTime`] encoding with 4-digit year. - #[asn1(type = "GeneralizedTime")] - GeneralTime(GeneralizedTime), -} - -impl Time { - /// Get duration since `UNIX_EPOCH`. - pub fn unix_duration(self) -> Duration { - match self { - Time::UtcTime(t) => t.unix_duration(), - Time::GeneralTime(t) => t.unix_duration(), - } - } -} diff --git a/zeroize/CHANGELOG.md b/zeroize/CHANGELOG.md new file mode 100644 index 00000000..6615e378 --- /dev/null +++ b/zeroize/CHANGELOG.md @@ -0,0 +1,187 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 1.5.4 (2022-03-16) +### Added +- Nightly-only upport for zeroizing ARM64 SIMD registers ([#749]) + +[#749]: https://github.com/RustCrypto/utils/pull/749 + +## 1.5.3 (2022-02-25) +### Fixed +- Deriving `ZeroizeOnDrop` on `DerefMut` ([#739]) + +[#739]: https://github.com/RustCrypto/utils/pull/739 + +## 1.5.2 (2022-01-31) [YANKED] +### Fixed +- Ambiguous method for `AssertZeroizeOnDrop` ([#725]) + +[#725]: https://github.com/RustCrypto/utils/pull/725 + +## 1.5.1 (2022-01-27) [YANKED] +### Fixed +- Double `mut` on `AssertZeroizeOnDrop` ([#719]) + +[#719]: https://github.com/RustCrypto/utils/pull/719 + +## 1.5.0 (2022-01-14) [YANKED] +### Added +- `Zeroize` impls for `PhantomData`, `PhantomPinned`, and tuples with 0-10 elements ([#660]) +- `#[zeroize(bound = "T: MyTrait")]` ([#663]) +- `ZeroizeOnDrop` trait and custom derive ([#699], [#700], [#703]) + +[#660]: https://github.com/RustCrypto/utils/pull/660 +[#663]: https://github.com/RustCrypto/utils/pull/663 +[#699]: https://github.com/RustCrypto/utils/pull/699 +[#700]: https://github.com/RustCrypto/utils/pull/700 +[#703]: https://github.com/RustCrypto/utils/pull/703 + +## 1.4.3 (2021-11-04) +### Added +- Implement `Zeroize` for `NonZeroX` + +### Changed +- Moved to `RustCrypto/utils` repository + +## 1.4.2 (2021-09-21) +### Added +- Derive `Default` on `Zeroizing` + +## 1.4.1 (2021-07-20) +### Added +- Implement Zeroize for `[MaybeUninit]` + +## 1.4.0 (2021-07-18) +NOTE: This release includes an MSRV bump to Rust 1.51. Please use `zeroize = "1.3.0"` +if you would like to support older Rust versions. + +### Added +- Use const generics to impl `Zeroize` for `[Z; N]`; MSRV 1.51 +- `Zeroizing::clone_from` now zeroizes the destination before cloning + +## 1.3.0 (2021-04-19) +### Added +- impl `Zeroize` for `Box<[Z]>` +- Clear residual space within `Option + +### Changed +- Ensure `Option` is `None` when zeroized +- Bump MSRV to 1.47 + +## 1.2.0 (2020-12-09) +### Added +- `Zeroize` support for x86(_64) SIMD registers + +### Changed +- Simplify `String::zeroize` +- MSRV 1.44+ + +## 1.1.1 (2020-09-15) +- Add `doc_cfg` +- zeroize entire capacity of `String` +- zeroize entire capacity of `Vec` + +## 1.1.0 (2019-12-02) +- Add `TryZeroize` trait +- Add `From` impl for `Zeroizing` +- Remove `bytes-preview` feature + +## 1.0.0 (2019-10-13) +- Initial 1.0 release 🎉 +- zeroize_derive: Remove legacy `no_drop` attribute support +- Rename `bytes` feature to `bytes-preview` +- Further relax `Zeroize` trait bounds for `Vec` +- Derive `Clone`, `Debug`, and `Eq` for `Zeroizing` + +## 1.0.0-pre (2019-09-30) +- Loosen `Vec` trait bounds for `Zeroize` + +## 0.10.1 (2019-09-03) +- (Optionally) Impl `Zeroize` for `Bytes` and `BytesMut` + +## 0.10.0 (2019-08-19) +Barring unforeseen circumstances, this release aims to be the last `0.x` +release prior to a `zeroize` 1.0 release. + +- Disable `zeroize_derive` Cargo feature by default +- Remove `std` feature in favor of `alloc`; MSRV 1.36+ +- Deprecate `#[zeroize(no_drop)]` attribute +- Use 1.0 `proc-macro2`, `quote`, and `syn` crates + +## 0.9.3 (2019-07-27) +- Improved attribute parser; fixes nightly build + +## 0.9.2 (2019-06-28) +- README.md: add Gitter badges; update image links + +## 0.9.1 (2019-06-04) +- Impl `Zeroize` for `Option` + +## 0.9.0 (2019-06-04) +**NOTICE**: This release changes the default behavior of `derive(Zeroize)` +to no longer derive a `Drop` impl. If you wish to derive `Drop`, you must +now explicitly add a `#[zeroize(drop)]` attribute on the type for which you +are deriving `Zeroize`. + +- Remove CPU fences +- Remove scary language about undefined behavior +- Bound blanket array impls on `Zeroize` instead of `DefaultIsZeroes` +- Require `zeroize(drop)` or `zeroize(no_drop)` attributes when deriving + `Zeroize` . +- Support stablized 'alloc' crate + +## 0.8.0 (2019-05-20) +- Impl `Drop` by default when deriving `Zeroize` + +## 0.7.0 (2019-05-19) +- Use synstructure for custom derive +- Add explicit array impls for `DefaultIsZeroes` +- Remove `nightly` feature +- Add `Zeroizing` to zeroize values on drop + +## 0.6.0 (2019-03-23) +- Add ZeroizeOnDrop marker trait + custom derive +- Custom derive support for `Zeroize` +- Rename `ZeroizeWithDefault` to `DefaultIsZeroes` + +## 0.5.2 (2018-12-25) +- Add `debug_assert!` to ensure string interiors are zeroized + +## 0.5.1 (2018-12-24) +- Avoid re-exporting the whole prelude + +## 0.5.0 (2018-12-24) +This release is a rewrite which replaces FFI bindings to OS-specific APIs with +a pure Rust solution. + +- Use `core::sync::atomic` fences +- Test wasm target +- Rewrite using `core::ptr::write_volatile` + +## 0.4.2 (2018-10-12) +- Fix ldd scraper for older glibc versions + +## 0.4.1 (2018-10-12) +- Support musl-libc + +## 0.4.0 (2018-10-12) +- Impl `Zeroize` trait on concrete types + +## 0.3.0 (2018-10-11) +- Replace `secure_zero_memory` with `Zeroize` + +## 0.2.0 (2018-10-11) +- Add `Zeroize` trait + +## 0.1.2 (2018-10-03) +- README.md: Fix intrinsic links + +## 0.1.1 (2018-10-03) +- Documentation improvements + +## 0.1.0 (2018-10-03) +- Initial release diff --git a/zeroize/Cargo.toml b/zeroize/Cargo.toml new file mode 100644 index 00000000..29d6cd99 --- /dev/null +++ b/zeroize/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "zeroize" +description = """ +Securely clear secrets from memory with a simple trait built on +stable Rust primitives which guarantee memory is zeroed using an +operation will not be 'optimized away' by the compiler. +Uses a portable pure Rust implementation that works everywhere, +even WASM! +""" +version = "1.5.4" +authors = ["The RustCrypto Project Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/utils/tree/master/zeroize" +readme = "README.md" +categories = ["cryptography", "memory-management", "no-std", "os"] +keywords = ["memory", "memset", "secure", "volatile", "zero"] +edition = "2018" + +[dependencies] +zeroize_derive = { version = "1.3", path = "derive", optional = true } + +[features] +default = ["alloc"] +aarch64 = [] +alloc = [] +derive = ["zeroize_derive"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/zeroize/LICENSE-APACHE b/zeroize/LICENSE-APACHE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/zeroize/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/zeroize/LICENSE-MIT b/zeroize/LICENSE-MIT new file mode 100644 index 00000000..3a2b0041 --- /dev/null +++ b/zeroize/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2021 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/zeroize/README.md b/zeroize/README.md new file mode 100644 index 00000000..4f71ab43 --- /dev/null +++ b/zeroize/README.md @@ -0,0 +1,80 @@ +# [RustCrypto]: zeroize + +[![Crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache 2.0/MIT Licensed][license-image] +![MSRV][rustc-image] +[![Build Status][build-image]][build-link] + +Securely zero memory (a.k.a. [zeroize]) while avoiding compiler optimizations. + +This crate implements a portable approach to securely zeroing memory using +techniques which guarantee they won't be "optimized away" by the compiler. + +The [`Zeroize` trait] is the crate's primary API. + +[Documentation] + +## About + +[Zeroing memory securely is hard] - compilers optimize for performance, and +in doing so they love to "optimize away" unnecessary zeroing calls. There are +many documented "tricks" to attempt to avoid these optimizations and ensure +that a zeroing routine is performed reliably. + +This crate isn't about tricks: it uses [core::ptr::write_volatile] +and [core::sync::atomic] memory fences to provide easy-to-use, portable +zeroing behavior which works on all of Rust's core number types and slices +thereof, implemented in pure Rust with no usage of FFI or assembly. + +- No insecure fallbacks! +- No dependencies! +- No FFI or inline assembly! **WASM friendly** (and tested)! +- `#![no_std]` i.e. **embedded-friendly**! +- No functionality besides securely zeroing memory! +- (Optional) Custom derive support for zeroing complex structures + +## Minimum Supported Rust Version + +Rust **1.51** or newer. + +In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope +for this crate's SemVer guarantees), however when we do it will be accompanied by +a minor version bump. + +## License + +Licensed under either of: + +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/zeroize.svg +[crate-link]: https://crates.io/crates/zeroize +[docs-image]: https://docs.rs/zeroize/badge.svg +[docs-link]: https://docs.rs/zeroize/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg +[build-image]: https://github.com/RustCrypto/utils/actions/workflows/zeroize.yml/badge.svg +[build-link]: https://github.com/RustCrypto/utils/actions/workflows/zeroize.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[zeroize]: https://en.wikipedia.org/wiki/Zeroisation +[`Zeroize` trait]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html +[Documentation]: https://docs.rs/zeroize/ +[Zeroing memory securely is hard]: http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html +[core::ptr::write_volatile]: https://doc.rust-lang.org/core/ptr/fn.write_volatile.html +[core::sync::atomic]: https://doc.rust-lang.org/stable/core/sync/atomic/index.html +[good cryptographic hygiene]: https://github.com/veorq/cryptocoding#clean-memory-of-secret-data diff --git a/zeroize/derive/CHANGELOG.md b/zeroize/derive/CHANGELOG.md new file mode 100644 index 00000000..1ecaef26 --- /dev/null +++ b/zeroize/derive/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 1.3.2 (2022-02-18) +### Fixed +- Min versions build ([#732]) + +[#732]: https://github.com/RustCrypto/utils/pull/732 + +## 1.3.1 (2021-01-14) [YANKED] +### Removed +- `ZeroizeOnDrop` implementation for `#[zeroize(drop)]` ([#715]) + +[#715]: https://github.com/RustCrypto/utils/pull/715 + +## 1.3.0 (2021-01-14) [YANKED] +### Added +- `#[zeroize(bound = "T: MyTrait")]` ([#663]) +- Custom derive for `ZeroizeOnDrop` ([#699], [#700]) + +[#663]: https://github.com/RustCrypto/utils/pull/663 +[#699]: https://github.com/RustCrypto/utils/pull/699 +[#700]: https://github.com/RustCrypto/utils/pull/700 + +## 1.2.2 (2021-11-04) +### Added +- `#[zeroize(skip)]` attribute ([#654]) + +[#654]: https://github.com/RustCrypto/utils/pull/654 + +## 1.2.1 (2021-11-04) +### Changed +- Moved to `RustCrypto/utils` repository + +## 1.2.0 (2021-09-21) +### Changed +- Bump MSRV to 1.51+ +- Reject `#[zeroize(drop)]` on struct/enum fields, enum variants + +## 1.1.1 (2021-10-09) +### Changed +- Backport 1.2.0 `#[zeroize(drop)]` fixes but with a 1.47+ MSRV. + +## 1.1.0 (2021-04-19) +### Changed +- Bump MSRV to 1.47+ + +## 1.0.1 (2019-09-15) +### Added +- Add docs for the `Zeroize` proc macro + +## 1.0.0 (2019-10-13) + +- Initial 1.0 release diff --git a/zeroize/derive/Cargo.toml b/zeroize/derive/Cargo.toml new file mode 100644 index 00000000..4bac91a3 --- /dev/null +++ b/zeroize/derive/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "zeroize_derive" +description = "Custom derive support for zeroize" +version = "1.3.2" +authors = ["The RustCrypto Project Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/utils/tree/master/zeroize/derive" +readme = "README.md" +categories = ["cryptography", "memory-management", "no-std", "os"] +keywords = ["memory", "memset", "secure", "volatile", "zero"] +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1" +quote = "1" +syn = "1" +synstructure = "0.12.2" + +[package.metadata.docs.rs] +rustdoc-args = ["--document-private-items"] diff --git a/zeroize/derive/LICENSE-APACHE b/zeroize/derive/LICENSE-APACHE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/zeroize/derive/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/zeroize/derive/LICENSE-MIT b/zeroize/derive/LICENSE-MIT new file mode 100644 index 00000000..42816283 --- /dev/null +++ b/zeroize/derive/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-2021 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/zeroize/derive/README.md b/zeroize/derive/README.md new file mode 100644 index 00000000..d5b16e37 --- /dev/null +++ b/zeroize/derive/README.md @@ -0,0 +1,49 @@ +# [RustCrypto]: `zeroize_derive` + +[![Crate][crate-image]][crate-link] +![Apache 2.0 Licensed/MIT][license-image] +![MSRV][rustc-image] +[![Build Status][build-image]][build-link] + +Custom derive support for [zeroize]: a crate for securely zeroing memory +while avoiding compiler optimizations. + +This crate isn't intended to be used directly. +See [zeroize] crate for documentation. + +## Minimum Supported Rust Version + +Rust **1.51** or newer. + +In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope +for this crate's SemVer guarantees), however when we do it will be accompanied by +a minor version bump. + +## License + +Licensed under either of: + +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/zeroize_derive.svg +[crate-link]: https://crates.io/crates/zeroize_derive +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.51+-blue.svg +[build-image]: https://github.com/RustCrypto/utils/actions/workflows/zeroize.yml/badge.svg +[build-link]: https://github.com/RustCrypto/utils/actions/workflows/zeroize.yml + +[//]: # (general links) + +[RustCrypto]: https://github.com/RustCrypto +[zeroize]: https://github.com/RustCrypto/utils/tree/master/zeroize diff --git a/zeroize/derive/src/lib.rs b/zeroize/derive/src/lib.rs new file mode 100644 index 00000000..1efc3b4d --- /dev/null +++ b/zeroize/derive/src/lib.rs @@ -0,0 +1,810 @@ +//! Custom derive support for `zeroize` + +#![crate_type = "proc-macro"] +#![forbid(unsafe_code)] +#![warn(rust_2018_idioms, trivial_casts, unused_qualifications)] + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token::Comma, + Attribute, Lit, Meta, NestedMeta, Result, WherePredicate, +}; +use synstructure::{decl_derive, AddBounds, BindStyle, BindingInfo, VariantInfo}; + +decl_derive!( + [Zeroize, attributes(zeroize)] => + + /// Derive the `Zeroize` trait. + /// + /// Supports the following attributes: + /// + /// On the item level: + /// - `#[zeroize(drop)]`: *deprecated* use `ZeroizeOnDrop` instead + /// - `#[zeroize(bound = "T: MyTrait")]`: this replaces any trait bounds + /// inferred by zeroize-derive + /// + /// On the field level: + /// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` + derive_zeroize +); + +decl_derive!( + [ZeroizeOnDrop, attributes(zeroize)] => + + /// Derive the `ZeroizeOnDrop` trait. + /// + /// Supports the following attributes: + /// + /// On the field level: + /// - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` + derive_zeroize_on_drop +); + +/// Name of zeroize-related attributes +const ZEROIZE_ATTR: &str = "zeroize"; + +/// Custom derive for `Zeroize` +fn derive_zeroize(mut s: synstructure::Structure<'_>) -> TokenStream { + let attributes = ZeroizeAttrs::parse(&s); + + if let Some(bounds) = attributes.bound { + s.add_bounds(AddBounds::None); + + for bound in bounds.0 { + s.add_where_predicate(bound); + } + } + + // NOTE: These are split into named functions to simplify testing with + // synstructure's `test_derive!` macro. + if attributes.drop { + derive_zeroize_with_drop(s) + } else { + derive_zeroize_without_drop(s) + } +} + +/// Custom derive for `ZeroizeOnDrop` +fn derive_zeroize_on_drop(mut s: synstructure::Structure<'_>) -> TokenStream { + let zeroizers = generate_fields(&mut s, quote! { zeroize_or_on_drop }); + + let drop_impl = s.gen_impl(quote! { + gen impl Drop for @Self { + fn drop(&mut self) { + use zeroize::__internal::AssertZeroize; + use zeroize::__internal::AssertZeroizeOnDrop; + match self { + #zeroizers + } + } + } + }); + + let zeroize_on_drop_impl = impl_zeroize_on_drop(&s); + + quote! { + #drop_impl + + #zeroize_on_drop_impl + } +} + +/// Custom derive attributes for `Zeroize` +#[derive(Default)] +struct ZeroizeAttrs { + /// Derive a `Drop` impl which calls zeroize on this type + drop: bool, + /// Custom bounds as defined by the user + bound: Option, +} + +/// Parsing helper for custom bounds +struct Bounds(Punctuated); + +impl Parse for Bounds { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self(Punctuated::parse_terminated(input)?)) + } +} + +impl ZeroizeAttrs { + /// Parse attributes from the incoming AST + fn parse(s: &synstructure::Structure<'_>) -> Self { + let mut result = Self::default(); + + for attr in s.ast().attrs.iter() { + result.parse_attr(attr, None, None); + } + for v in s.variants().iter() { + // only process actual enum variants here, as we don't want to process struct attributes twice + if v.prefix.is_some() { + for attr in v.ast().attrs.iter() { + result.parse_attr(attr, Some(v), None); + } + } + for binding in v.bindings().iter() { + for attr in binding.ast().attrs.iter() { + result.parse_attr(attr, Some(v), Some(binding)); + } + } + } + + result + } + + /// Parse attribute and handle `#[zeroize(...)]` attributes + fn parse_attr( + &mut self, + attr: &Attribute, + variant: Option<&VariantInfo<'_>>, + binding: Option<&BindingInfo<'_>>, + ) { + let meta_list = match attr + .parse_meta() + .unwrap_or_else(|e| panic!("error parsing attribute: {:?} ({})", attr, e)) + { + Meta::List(list) => list, + _ => return, + }; + + // Ignore any non-zeroize attributes + if !meta_list.path.is_ident(ZEROIZE_ATTR) { + return; + } + + for nested_meta in &meta_list.nested { + if let NestedMeta::Meta(meta) = nested_meta { + self.parse_meta(meta, variant, binding); + } else { + panic!("malformed #[zeroize] attribute: {:?}", nested_meta); + } + } + } + + /// Parse `#[zeroize(...)]` attribute metadata (e.g. `drop`) + fn parse_meta( + &mut self, + meta: &Meta, + variant: Option<&VariantInfo<'_>>, + binding: Option<&BindingInfo<'_>>, + ) { + if meta.path().is_ident("drop") { + assert!(!self.drop, "duplicate #[zeroize] drop flags"); + + match (variant, binding) { + (_variant, Some(_binding)) => { + // structs don't have a variant prefix, and only structs have bindings outside of a variant + let item_kind = match variant.and_then(|variant| variant.prefix) { + Some(_) => "enum", + None => "struct", + }; + panic!( + concat!( + "The #[zeroize(drop)] attribute is not allowed on {} fields. ", + "Use it on the containing {} instead.", + ), + item_kind, item_kind, + ) + } + (Some(_variant), None) => panic!(concat!( + "The #[zeroize(drop)] attribute is not allowed on enum variants. ", + "Use it on the containing enum instead.", + )), + (None, None) => (), + }; + + self.drop = true; + } else if meta.path().is_ident("bound") { + assert!(self.bound.is_none(), "duplicate #[zeroize] bound flags"); + + match (variant, binding) { + (_variant, Some(_binding)) => { + // structs don't have a variant prefix, and only structs have bindings outside of a variant + let item_kind = match variant.and_then(|variant| variant.prefix) { + Some(_) => "enum", + None => "struct", + }; + panic!( + concat!( + "The #[zeroize(bound)] attribute is not allowed on {} fields. ", + "Use it on the containing {} instead.", + ), + item_kind, item_kind, + ) + } + (Some(_variant), None) => panic!(concat!( + "The #[zeroize(bound)] attribute is not allowed on enum variants. ", + "Use it on the containing enum instead.", + )), + (None, None) => { + if let Meta::NameValue(meta_name_value) = meta { + if let Lit::Str(lit) = &meta_name_value.lit { + if lit.value().is_empty() { + self.bound = Some(Bounds(Punctuated::new())); + } else { + self.bound = Some(lit.parse().unwrap_or_else(|e| { + panic!("error parsing bounds: {:?} ({})", lit, e) + })); + } + + return; + } + } + + panic!(concat!( + "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.", + "E.g. #[zeroize(bound = \"T: MyTrait\")]." + )) + } + } + } else if meta.path().is_ident("skip") { + if variant.is_none() && binding.is_none() { + panic!(concat!( + "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. ", + "Use it on a field or variant instead.", + )) + } + } else { + panic!("unknown #[zeroize] attribute type: {:?}", meta.path()); + } + } +} + +fn generate_fields(s: &mut synstructure::Structure<'_>, method: TokenStream) -> TokenStream { + s.bind_with(|_| BindStyle::RefMut); + + s.filter_variants(|vi| { + let result = filter_skip(vi.ast().attrs, true); + + // check for duplicate `#[zeroize(skip)]` attributes in nested variants + for field in vi.ast().fields { + filter_skip(&field.attrs, result); + } + + result + }) + .filter(|bi| filter_skip(&bi.ast().attrs, true)) + .each(|bi| quote! { #bi.#method(); }) +} + +fn filter_skip(attrs: &[Attribute], start: bool) -> bool { + let mut result = start; + + for attr in attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { + if let Meta::List(list) = attr { + if list.path.is_ident(ZEROIZE_ATTR) { + for nested in list.nested { + if let NestedMeta::Meta(Meta::Path(path)) = nested { + if path.is_ident("skip") { + assert!(result, "duplicate #[zeroize] skip flags"); + result = false; + } + } + } + } + } + } + + result +} + +/// Custom derive for `Zeroize` (without `Drop`) +fn derive_zeroize_without_drop(mut s: synstructure::Structure<'_>) -> TokenStream { + let zeroizers = generate_fields(&mut s, quote! { zeroize }); + + s.bound_impl( + quote!(zeroize::Zeroize), + quote! { + fn zeroize(&mut self) { + match self { + #zeroizers + } + } + }, + ) +} + +/// Custom derive for `Zeroize` and `Drop` +fn derive_zeroize_with_drop(s: synstructure::Structure<'_>) -> TokenStream { + let drop_impl = s.gen_impl(quote! { + gen impl Drop for @Self { + fn drop(&mut self) { + self.zeroize(); + } + } + }); + + let zeroize_impl = derive_zeroize_without_drop(s); + + quote! { + #zeroize_impl + + #[doc(hidden)] + #drop_impl + } +} + +fn impl_zeroize_on_drop(s: &synstructure::Structure<'_>) -> TokenStream { + #[allow(unused_qualifications)] + s.bound_impl(quote!(zeroize::ZeroizeOnDrop), Option::::None) +} + +#[cfg(test)] +mod tests { + use super::*; + use syn::parse_str; + use synstructure::{test_derive, Structure}; + + #[test] + fn zeroize_without_drop() { + test_derive! { + derive_zeroize_without_drop { + struct Z { + a: String, + b: Vec, + c: [u8; 3], + } + } + expands to { + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_Zeroize_FOR_Z: () = { + extern crate zeroize; + impl zeroize::Zeroize for Z { + fn zeroize(&mut self) { + match self { + Z { + a: ref mut __binding_0, + b: ref mut __binding_1, + c: ref mut __binding_2, + } => { + { __binding_0.zeroize(); } + { __binding_1.zeroize(); } + { __binding_2.zeroize(); } + } + } + } + } + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + + #[test] + fn zeroize_with_drop() { + test_derive! { + derive_zeroize_with_drop { + struct Z { + a: String, + b: Vec, + c: [u8; 3], + } + } + expands to { + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_Zeroize_FOR_Z: () = { + extern crate zeroize; + impl zeroize::Zeroize for Z { + fn zeroize(&mut self) { + match self { + Z { + a: ref mut __binding_0, + b: ref mut __binding_1, + c: ref mut __binding_2, + } => { + { __binding_0.zeroize(); } + { __binding_1.zeroize(); } + { __binding_2.zeroize(); } + } + } + } + } + }; + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const _DERIVE_Drop_FOR_Z: () = { + impl Drop for Z { + fn drop(&mut self) { + self.zeroize(); + } + } + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + + #[test] + fn zeroize_with_skip() { + test_derive! { + derive_zeroize_without_drop { + struct Z { + a: String, + b: Vec, + #[zeroize(skip)] + c: [u8; 3], + } + } + expands to { + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_Zeroize_FOR_Z: () = { + extern crate zeroize; + impl zeroize::Zeroize for Z { + fn zeroize(&mut self) { + match self { + Z { + a: ref mut __binding_0, + b: ref mut __binding_1, + .. + } => { + { __binding_0.zeroize(); } + { __binding_1.zeroize(); } + } + } + } + } + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + + #[test] + fn zeroize_with_bound() { + test_derive! { + derive_zeroize { + #[zeroize(bound = "T: MyTrait")] + struct Z(T); + } + expands to { + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_Zeroize_FOR_Z: () = { + extern crate zeroize; + impl zeroize::Zeroize for Z + where T: MyTrait + { + fn zeroize(&mut self) { + match self { + Z(ref mut __binding_0,) => { + { __binding_0.zeroize(); } + } + } + } + } + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + + #[test] + fn zeroize_only_drop() { + test_derive! { + derive_zeroize_on_drop { + struct Z { + a: String, + b: Vec, + c: [u8; 3], + } + } + expands to { + #[allow(non_upper_case_globals)] + const _DERIVE_Drop_FOR_Z: () = { + impl Drop for Z { + fn drop(&mut self) { + use zeroize::__internal::AssertZeroize; + use zeroize::__internal::AssertZeroizeOnDrop; + match self { + Z { + a: ref mut __binding_0, + b: ref mut __binding_1, + c: ref mut __binding_2, + } => { + { __binding_0.zeroize_or_on_drop(); } + { __binding_1.zeroize_or_on_drop(); } + { __binding_2.zeroize_or_on_drop(); } + } + } + } + } + }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + const _DERIVE_zeroize_ZeroizeOnDrop_FOR_Z: () = { + extern crate zeroize; + impl zeroize::ZeroizeOnDrop for Z {} + }; + } + no_build // tests the code compiles are in the `zeroize` crate + } + } + + #[test] + fn zeroize_on_struct() { + parse_zeroize_test(stringify!( + #[zeroize(drop)] + struct Z { + a: String, + b: Vec, + c: [u8; 3], + } + )); + } + + #[test] + fn zeroize_on_enum() { + parse_zeroize_test(stringify!( + #[zeroize(drop)] + enum Z { + Variant1 { a: String, b: Vec, c: [u8; 3] }, + } + )); + } + + #[test] + #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")] + fn zeroize_on_struct_field() { + parse_zeroize_test(stringify!( + struct Z { + #[zeroize(drop)] + a: String, + b: Vec, + c: [u8; 3], + } + )); + } + + #[test] + #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")] + fn zeroize_on_tuple_struct_field() { + parse_zeroize_test(stringify!( + struct Z(#[zeroize(drop)] String); + )); + } + + #[test] + #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on struct fields")] + fn zeroize_on_second_field() { + parse_zeroize_test(stringify!( + struct Z { + a: String, + #[zeroize(drop)] + b: Vec, + c: [u8; 3], + } + )); + } + + #[test] + #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")] + fn zeroize_on_tuple_enum_variant_field() { + parse_zeroize_test(stringify!( + enum Z { + Variant(#[zeroize(drop)] String), + } + )); + } + + #[test] + #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")] + fn zeroize_on_enum_variant_field() { + parse_zeroize_test(stringify!( + enum Z { + Variant { + #[zeroize(drop)] + a: String, + b: Vec, + c: [u8; 3], + }, + } + )); + } + + #[test] + #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum fields")] + fn zeroize_on_enum_second_variant_field() { + parse_zeroize_test(stringify!( + enum Z { + Variant1 { + a: String, + b: Vec, + c: [u8; 3], + }, + Variant2 { + #[zeroize(drop)] + a: String, + b: Vec, + c: [u8; 3], + }, + } + )); + } + + #[test] + #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")] + fn zeroize_on_enum_variant() { + parse_zeroize_test(stringify!( + enum Z { + #[zeroize(drop)] + Variant, + } + )); + } + + #[test] + #[should_panic(expected = "#[zeroize(drop)] attribute is not allowed on enum variants")] + fn zeroize_on_enum_second_variant() { + parse_zeroize_test(stringify!( + enum Z { + Variant1, + #[zeroize(drop)] + Variant2, + } + )); + } + + #[test] + #[should_panic( + expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead." + )] + fn zeroize_skip_on_struct() { + parse_zeroize_test(stringify!( + #[zeroize(skip)] + struct Z { + a: String, + b: Vec, + c: [u8; 3], + } + )); + } + + #[test] + #[should_panic( + expected = "The #[zeroize(skip)] attribute is not allowed on a `struct` or `enum`. Use it on a field or variant instead." + )] + fn zeroize_skip_on_enum() { + parse_zeroize_test(stringify!( + #[zeroize(skip)] + enum Z { + Variant1, + Variant2, + } + )); + } + + #[test] + #[should_panic(expected = "duplicate #[zeroize] skip flags")] + fn zeroize_duplicate_skip() { + parse_zeroize_test(stringify!( + struct Z { + a: String, + #[zeroize(skip)] + #[zeroize(skip)] + b: Vec, + c: [u8; 3], + } + )); + } + + #[test] + #[should_panic(expected = "duplicate #[zeroize] skip flags")] + fn zeroize_duplicate_skip_list() { + parse_zeroize_test(stringify!( + struct Z { + a: String, + #[zeroize(skip, skip)] + b: Vec, + c: [u8; 3], + } + )); + } + + #[test] + #[should_panic(expected = "duplicate #[zeroize] skip flags")] + fn zeroize_duplicate_skip_enum() { + parse_zeroize_test(stringify!( + enum Z { + #[zeroize(skip)] + Variant { + a: String, + #[zeroize(skip)] + b: Vec, + c: [u8; 3], + }, + } + )); + } + + #[test] + #[should_panic(expected = "duplicate #[zeroize] bound flags")] + fn zeroize_duplicate_bound() { + parse_zeroize_test(stringify!( + #[zeroize(bound = "T: MyTrait")] + #[zeroize(bound = "")] + struct Z(T); + )); + } + + #[test] + #[should_panic(expected = "duplicate #[zeroize] bound flags")] + fn zeroize_duplicate_bound_list() { + parse_zeroize_test(stringify!( + #[zeroize(bound = "T: MyTrait", bound = "")] + struct Z(T); + )); + } + + #[test] + #[should_panic( + expected = "The #[zeroize(bound)] attribute is not allowed on struct fields. Use it on the containing struct instead." + )] + fn zeroize_bound_struct() { + parse_zeroize_test(stringify!( + struct Z { + #[zeroize(bound = "T: MyTrait")] + a: T, + } + )); + } + + #[test] + #[should_panic( + expected = "The #[zeroize(bound)] attribute is not allowed on enum variants. Use it on the containing enum instead." + )] + fn zeroize_bound_enum() { + parse_zeroize_test(stringify!( + enum Z { + #[zeroize(bound = "T: MyTrait")] + A(T), + } + )); + } + + #[test] + #[should_panic( + expected = "The #[zeroize(bound)] attribute is not allowed on enum fields. Use it on the containing enum instead." + )] + fn zeroize_bound_enum_variant_field() { + parse_zeroize_test(stringify!( + enum Z { + A { + #[zeroize(bound = "T: MyTrait")] + a: T, + }, + } + )); + } + + #[test] + #[should_panic( + expected = "The #[zeroize(bound)] attribute expects a name-value syntax with a string literal value.E.g. #[zeroize(bound = \"T: MyTrait\")]." + )] + fn zeroize_bound_no_value() { + parse_zeroize_test(stringify!( + #[zeroize(bound)] + struct Z(T); + )); + } + + #[test] + #[should_panic(expected = "error parsing bounds: LitStr { token: \"T\" } (expected `:`)")] + fn zeroize_bound_no_where_predicate() { + parse_zeroize_test(stringify!( + #[zeroize(bound = "T")] + struct Z(T); + )); + } + + fn parse_zeroize_test(unparsed: &str) -> TokenStream { + derive_zeroize(Structure::new( + &parse_str(unparsed).expect("Failed to parse test input"), + )) + } +} diff --git a/zeroize/src/aarch64.rs b/zeroize/src/aarch64.rs new file mode 100644 index 00000000..fc6c8f23 --- /dev/null +++ b/zeroize/src/aarch64.rs @@ -0,0 +1,35 @@ +//! [`Zeroize`] impls for ARM64 SIMD registers. +//! +//! Support for this is gated behind an `aarch64` feature because +//! support for `core::arch::aarch64` is currently nightly-only. + +use crate::{atomic_fence, volatile_write, Zeroize}; + +use core::arch::aarch64::*; + +macro_rules! impl_zeroize_for_simd_register { + ($(($type:ty, $vdupq:ident)),+) => { + $( + #[cfg_attr(docsrs, doc(cfg(target_arch = "aarch64")))] + #[cfg_attr(docsrs, doc(cfg(target_feature = "neon")))] + impl Zeroize for $type { + fn zeroize(&mut self) { + volatile_write(self, unsafe { $vdupq(0) }); + atomic_fence(); + } + } + )+ + }; +} + +// TODO(tarcieri): other NEON register types? +impl_zeroize_for_simd_register! { + (uint8x8_t, vdup_n_u8), + (uint8x16_t, vdupq_n_u8), + (uint16x4_t, vdup_n_u16), + (uint16x8_t, vdupq_n_u16), + (uint32x2_t, vdup_n_u32), + (uint32x4_t, vdupq_n_u32), + (uint64x1_t, vdup_n_u64), + (uint64x2_t, vdupq_n_u64) +} diff --git a/zeroize/src/lib.rs b/zeroize/src/lib.rs new file mode 100644 index 00000000..5006fb61 --- /dev/null +++ b/zeroize/src/lib.rs @@ -0,0 +1,868 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" +)] +#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] + +//! Securely zero memory with a simple trait ([`Zeroize`]) built on stable Rust +//! primitives which guarantee the operation will not be "optimized away". +//! +//! ## About +//! +//! [Zeroing memory securely is hard] - compilers optimize for performance, and +//! in doing so they love to "optimize away" unnecessary zeroing calls. There are +//! many documented "tricks" to attempt to avoid these optimizations and ensure +//! that a zeroing routine is performed reliably. +//! +//! This crate isn't about tricks: it uses [`core::ptr::write_volatile`] +//! and [`core::sync::atomic`] memory fences to provide easy-to-use, portable +//! zeroing behavior which works on all of Rust's core number types and slices +//! thereof, implemented in pure Rust with no usage of FFI or assembly. +//! +//! - No insecure fallbacks! +//! - No dependencies! +//! - No FFI or inline assembly! **WASM friendly** (and tested)! +//! - `#![no_std]` i.e. **embedded-friendly**! +//! - No functionality besides securely zeroing memory! +//! - (Optional) Custom derive support for zeroing complex structures +//! +//! ## Minimum Supported Rust Version +//! +//! Requires Rust **1.51** or newer. +//! +//! In the future, we reserve the right to change MSRV (i.e. MSRV is out-of-scope +//! for this crate's SemVer guarantees), however when we do it will be accompanied +//! by a minor version bump. +//! +//! ## Usage +//! +//! ``` +//! use zeroize::Zeroize; +//! +//! fn main() { +//! // Protip: don't embed secrets in your source code. +//! // This is just an example. +//! let mut secret = b"Air shield password: 1,2,3,4,5".to_vec(); +//! // [ ... ] open the air shield here +//! +//! // Now that we're done using the secret, zero it out. +//! secret.zeroize(); +//! } +//! ``` +//! +//! The [`Zeroize`] trait is impl'd on all of Rust's core scalar types including +//! integers, floats, `bool`, and `char`. +//! +//! Additionally, it's implemented on slices and `IterMut`s of the above types. +//! +//! When the `alloc` feature is enabled (which it is by default), it's also +//! impl'd for `Vec` for the above types as well as `String`, where it provides +//! [`Vec::clear`] / [`String::clear`]-like behavior (truncating to zero-length) +//! but ensures the backing memory is securely zeroed with some caveats. +//! (NOTE: see "Stack/Heap Zeroing Notes" for important `Vec`/`String` details) +//! +//! The [`DefaultIsZeroes`] marker trait can be impl'd on types which also +//! impl [`Default`], which implements [`Zeroize`] by overwriting a value with +//! the default value. +//! +//! ## Custom Derive Support +//! +//! This crate has custom derive support for the `Zeroize` trait, +//! gated under the `zeroize` crate's `zeroize_derive` Cargo feature, +//! which automatically calls `zeroize()` on all members of a struct +//! or tuple struct. +//! +//! Attributes supported for `Zeroize`: +//! +//! On the item level: +//! - `#[zeroize(drop)]`: *deprecated* use `ZeroizeOnDrop` instead +//! - `#[zeroize(bound = "T: MyTrait")]`: this replaces any trait bounds +//! inferred by zeroize +//! +//! On the field level: +//! - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` +//! +//! Attributes supported for `ZeroizeOnDrop`: +//! +//! On the field level: +//! - `#[zeroize(skip)]`: skips this field or variant when calling `zeroize()` +//! +//! Example which derives `Drop`: +//! +//! ``` +//! # #[cfg(feature = "zeroize_derive")] +//! # { +//! use zeroize::{Zeroize, ZeroizeOnDrop}; +//! +//! // This struct will be zeroized on drop +//! #[derive(Zeroize, ZeroizeOnDrop)] +//! struct MyStruct([u8; 32]); +//! # } +//! ``` +//! +//! Example which does not derive `Drop` (useful for e.g. `Copy` types) +//! +//! ``` +//! #[cfg(feature = "zeroize_derive")] +//! # { +//! use zeroize::Zeroize; +//! +//! // This struct will *NOT* be zeroized on drop +//! #[derive(Copy, Clone, Zeroize)] +//! struct MyStruct([u8; 32]); +//! # } +//! ``` +//! +//! Example which only derives `Drop`: +//! +//! ``` +//! # #[cfg(feature = "zeroize_derive")] +//! # { +//! use zeroize::ZeroizeOnDrop; +//! +//! // This struct will be zeroized on drop +//! #[derive(ZeroizeOnDrop)] +//! struct MyStruct([u8; 32]); +//! # } +//! ``` +//! +//! ## `Zeroizing`: wrapper for zeroizing arbitrary values on drop +//! +//! `Zeroizing` is a generic wrapper type that impls `Deref` +//! and `DerefMut`, allowing access to an inner value of type `Z`, and also +//! impls a `Drop` handler which calls `zeroize()` on its contents: +//! +//! ``` +//! use zeroize::Zeroizing; +//! +//! fn main() { +//! let mut secret = Zeroizing::new([0u8; 5]); +//! +//! // Set the air shield password +//! // Protip (again): don't embed secrets in your source code. +//! secret.copy_from_slice(&[1, 2, 3, 4, 5]); +//! assert_eq!(secret.as_ref(), &[1, 2, 3, 4, 5]); +//! +//! // The contents of `secret` will be automatically zeroized on drop +//! } +//! ``` +//! +//! ## What guarantees does this crate provide? +//! +//! This crate guarantees the following: +//! +//! 1. The zeroing operation can't be "optimized away" by the compiler. +//! 2. All subsequent reads to memory will see "zeroized" values. +//! +//! LLVM's volatile semantics ensure #1 is true. +//! +//! Additionally, thanks to work by the [Unsafe Code Guidelines Working Group], +//! we can now fairly confidently say #2 is true as well. Previously there were +//! worries that the approach used by this crate (mixing volatile and +//! non-volatile accesses) was undefined behavior due to language contained +//! in the documentation for `write_volatile`, however after some discussion +//! [these remarks have been removed] and the specific usage pattern in this +//! crate is considered to be well-defined. +//! +//! Additionally this crate leverages [`core::sync::atomic::compiler_fence`] +//! with the strictest ordering +//! ([`Ordering::SeqCst`]) as a +//! precaution to help ensure reads are not reordered before memory has been +//! zeroed. +//! +//! All of that said, there is still potential for microarchitectural attacks +//! (ala Spectre/Meltdown) to leak "zeroized" secrets through covert channels. +//! This crate makes no guarantees that zeroized values cannot be leaked +//! through such channels, as they represent flaws in the underlying hardware. +//! +//! ## Stack/Heap Zeroing Notes +//! +//! This crate can be used to zero values from either the stack or the heap. +//! +//! However, be aware several operations in Rust can unintentionally leave +//! copies of data in memory. This includes but is not limited to: +//! +//! - Moves and [`Copy`] +//! - Heap reallocation when using [`Vec`] and [`String`] +//! - Borrowers of a reference making copies of the data +//! +//! [`Pin`][`core::pin::Pin`] can be leveraged in conjunction with this crate +//! to ensure data kept on the stack isn't moved. +//! +//! The `Zeroize` impls for `Vec` and `String` zeroize the entire capacity of +//! their backing buffer, but cannot guarantee copies of the data were not +//! previously made by buffer reallocation. It's therefore important when +//! attempting to zeroize such buffers to initialize them to the correct +//! capacity, and take care to prevent subsequent reallocation. +//! +//! The `secrecy` crate provides higher-level abstractions for eliminating +//! usage patterns which can cause reallocations: +//! +//! +//! +//! ## What about: clearing registers, mlock, mprotect, etc? +//! +//! This crate is focused on providing simple, unobtrusive support for reliably +//! zeroing memory using the best approach possible on stable Rust. +//! +//! Clearing registers is a difficult problem that can't easily be solved by +//! something like a crate, and requires either inline ASM or rustc support. +//! See for background on +//! this particular problem. +//! +//! Other memory protection mechanisms are interesting and useful, but often +//! overkill (e.g. defending against RAM scraping or attackers with swap access). +//! In as much as there may be merit to these approaches, there are also many +//! other crates that already implement more sophisticated memory protections. +//! Such protections are explicitly out-of-scope for this crate. +//! +//! Zeroing memory is [good cryptographic hygiene] and this crate seeks to promote +//! it in the most unobtrusive manner possible. This includes omitting complex +//! `unsafe` memory protection systems and just trying to make the best memory +//! zeroing crate available. +//! +//! [Zeroing memory securely is hard]: http://www.daemonology.net/blog/2014-09-04-how-to-zero-a-buffer.html +//! [Unsafe Code Guidelines Working Group]: https://github.com/rust-lang/unsafe-code-guidelines +//! [these remarks have been removed]: https://github.com/rust-lang/rust/pull/60972 +//! [good cryptographic hygiene]: https://github.com/veorq/cryptocoding#clean-memory-of-secret-data +//! [`Ordering::SeqCst`]: core::sync::atomic::Ordering::SeqCst + +#[cfg(feature = "alloc")] +#[cfg_attr(test, macro_use)] +extern crate alloc; + +#[cfg(feature = "zeroize_derive")] +#[cfg_attr(docsrs, doc(cfg(feature = "zeroize_derive")))] +pub use zeroize_derive::{Zeroize, ZeroizeOnDrop}; + +#[cfg(all(feature = "aarch64", target_arch = "aarch64"))] +mod aarch64; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod x86; + +use core::marker::{PhantomData, PhantomPinned}; +use core::mem::{self, MaybeUninit}; +use core::num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, +}; +use core::{ops, ptr, slice::IterMut, sync::atomic}; + +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, string::String, vec::Vec}; + +/// Trait for securely erasing types from memory +pub trait Zeroize { + /// Zero out this object from memory using Rust intrinsics which ensure the + /// zeroization operation is not "optimized away" by the compiler. + fn zeroize(&mut self); +} + +/// Marker trait signifying that this type will [`zeroize`](Zeroize::zeroize) itself on [`Drop`]. +pub trait ZeroizeOnDrop {} + +#[doc(hidden)] +pub mod __internal { + use super::*; + + /// Auto-deref workaround for deriving `ZeroizeOnDrop`. + pub trait AssertZeroizeOnDrop { + fn zeroize_or_on_drop(self); + } + + impl AssertZeroizeOnDrop for &&mut T { + fn zeroize_or_on_drop(self) {} + } + + /// Auto-deref workaround for deriving `ZeroizeOnDrop`. + pub trait AssertZeroize { + fn zeroize_or_on_drop(&mut self); + } + + impl AssertZeroize for T { + fn zeroize_or_on_drop(&mut self) { + self.zeroize() + } + } +} + +/// Marker trait for types whose `Default` is the desired zeroization result +pub trait DefaultIsZeroes: Copy + Default + Sized {} + +impl Zeroize for Z +where + Z: DefaultIsZeroes, +{ + fn zeroize(&mut self) { + volatile_write(self, Z::default()); + atomic_fence(); + } +} + +macro_rules! impl_zeroize_with_default { + ($($type:ty),+) => { + $(impl DefaultIsZeroes for $type {})+ + }; +} + +impl_zeroize_with_default!(i8, i16, i32, i64, i128, isize); +impl_zeroize_with_default!(u8, u16, u32, u64, u128, usize); +impl_zeroize_with_default!(f32, f64, char, bool); + +macro_rules! impl_zeroize_for_non_zero { + ($($type:ty),+) => { + $(impl Zeroize for $type + { + fn zeroize(&mut self) { + volatile_write(self, unsafe { <$type>::new_unchecked(1) }); + atomic_fence(); + } + })+ + }; +} + +impl_zeroize_for_non_zero!( + NonZeroI8, + NonZeroI16, + NonZeroI32, + NonZeroI64, + NonZeroI128, + NonZeroIsize +); +impl_zeroize_for_non_zero!( + NonZeroU8, + NonZeroU16, + NonZeroU32, + NonZeroU64, + NonZeroU128, + NonZeroUsize +); + +/// Implement `Zeroize` on arrays of types that impl `Zeroize` +impl Zeroize for [Z; N] +where + Z: Zeroize, +{ + fn zeroize(&mut self) { + self.iter_mut().zeroize(); + } +} +/// Implement `ZeroizeOnDrop` on arrays of types that impl `ZeroizeOnDrop` +impl ZeroizeOnDrop for [Z; N] where Z: ZeroizeOnDrop {} + +impl<'a, Z> Zeroize for IterMut<'a, Z> +where + Z: Zeroize, +{ + fn zeroize(&mut self) { + for elem in self { + elem.zeroize(); + } + } +} + +impl Zeroize for Option +where + Z: Zeroize, +{ + fn zeroize(&mut self) { + if let Some(value) = self { + value.zeroize(); + + // Ensures self is None and that the value was dropped. Without the take, the drop + // of the (zeroized) value isn't called, which might lead to a leak or other + // unexpected behavior. For example, if this were Option>, the above call to + // zeroize would not free the allocated memory, but the the `take` call will. + self.take(); + } + + // Ensure that if the `Option` were previously `Some` but a value was copied/moved out + // that the remaining space in the `Option` is zeroized. + // + // Safety: + // + // The memory pointed to by `self` is valid for `mem::size_of::()` bytes. + // It is also properly aligned, because `u8` has an alignment of `1`. + unsafe { + volatile_set(self as *mut _ as *mut u8, 0, mem::size_of::()); + } + + // Ensures self is overwritten with the default bit pattern. volatile_write can't be + // used because Option is not copy. + // + // Safety: + // + // self is safe to replace with the default, which the take() call above should have + // already done semantically. Any value which needed to be dropped will have been + // done so by take(). + unsafe { ptr::write_volatile(self, Option::default()) } + + atomic_fence(); + } +} + +impl ZeroizeOnDrop for Option where Z: ZeroizeOnDrop {} + +/// Impl `Zeroize` on slices of MaybeUninit types +/// This impl can eventually be optimized using an memset intrinsic, +/// such as `core::intrinsics::volatile_set_memory`. +/// This fills the slice with zeros +/// Note that this ignore invariants that Z might have, because MaybeUninit removes all invariants. +impl Zeroize for [MaybeUninit] { + fn zeroize(&mut self) { + let ptr = self.as_mut_ptr() as *mut MaybeUninit; + let size = self.len().checked_mul(mem::size_of::()).unwrap(); + assert!(size <= core::isize::MAX as usize); + // Safety: + // + // This is safe, because every valid pointer is well aligned for u8 + // and it is backed by a single allocated object for at least `self.len() * size_pf::()` bytes. + // and 0 is a valid value for `MaybeUninit` + // The memory of the slice should not wrap around the address space. + unsafe { volatile_set(ptr, MaybeUninit::new(0), size) } + atomic_fence(); + } +} + +/// Impl `Zeroize` on slices of types that can be zeroized with `Default`. +/// +/// This impl can eventually be optimized using an memset intrinsic, +/// such as `core::intrinsics::volatile_set_memory`. For that reason the blanket +/// impl on slices is bounded by `DefaultIsZeroes`. +/// +/// To zeroize a mut slice of `Z: Zeroize` which does not impl +/// `DefaultIsZeroes`, call `iter_mut().zeroize()`. +impl Zeroize for [Z] +where + Z: DefaultIsZeroes, +{ + fn zeroize(&mut self) { + assert!(self.len() <= core::isize::MAX as usize); + // Safety: + // + // This is safe, because the slice is well aligned and is backed by a single allocated + // object for at least `self.len()` elements of type `Z`. + // `self.len()` is also not larger than an `isize`, because of the assertion above. + // The memory of the slice should not wrap around the address space. + unsafe { volatile_set(self.as_mut_ptr(), Z::default(), self.len()) }; + atomic_fence(); + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Zeroize for Vec +where + Z: Zeroize, +{ + /// "Best effort" zeroization for `Vec`. + /// + /// Ensures the entire capacity of the `Vec` is zeroed. Cannot ensure that + /// previous reallocations did not leave values on the heap. + fn zeroize(&mut self) { + use core::slice; + // Zeroize all the initialized elements. + self.iter_mut().zeroize(); + + // Set the Vec's length to 0 and drop all the elements. + self.clear(); + // Zero the full capacity of `Vec`. + // Safety: + // + // This is safe, because `Vec` never allocates more than `isize::MAX` bytes. + // This exact use case is even mentioned in the documentation of `pointer::add`. + // This is safe because MaybeUninit ignores all invariants, + // so we can create a slice of MaybeUninit using the full capacity of the Vec + let uninit_slice = unsafe { + slice::from_raw_parts_mut(self.as_mut_ptr() as *mut MaybeUninit, self.capacity()) + }; + uninit_slice.zeroize(); + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl ZeroizeOnDrop for Vec where Z: ZeroizeOnDrop {} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Zeroize for Box<[Z]> +where + Z: Zeroize, +{ + /// Unlike `Vec`, `Box<[Z]>` cannot reallocate, so we can be sure that we are not leaving + /// values on the heap. + fn zeroize(&mut self) { + self.iter_mut().zeroize(); + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl ZeroizeOnDrop for Box<[Z]> where Z: ZeroizeOnDrop {} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl Zeroize for String { + fn zeroize(&mut self) { + unsafe { self.as_mut_vec() }.zeroize(); + } +} + +/// Fallible trait for representing cases where zeroization may or may not be +/// possible. +/// +/// This is primarily useful for scenarios like reference counted data, where +/// zeroization is only possible when the last reference is dropped. +pub trait TryZeroize { + /// Try to zero out this object from memory using Rust intrinsics which + /// ensure the zeroization operation is not "optimized away" by the + /// compiler. + #[must_use] + fn try_zeroize(&mut self) -> bool; +} + +/// `Zeroizing` is a a wrapper for any `Z: Zeroize` type which implements a +/// `Drop` handler which zeroizes dropped values. +#[derive(Debug, Default, Eq, PartialEq)] +pub struct Zeroizing(Z); + +impl Zeroizing +where + Z: Zeroize, +{ + /// Move value inside a `Zeroizing` wrapper which ensures it will be + /// zeroized when it's dropped. + pub fn new(value: Z) -> Self { + value.into() + } +} + +impl Clone for Zeroizing { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + + fn clone_from(&mut self, source: &Self) { + self.0.zeroize(); + self.0.clone_from(&source.0); + } +} + +impl From for Zeroizing +where + Z: Zeroize, +{ + fn from(value: Z) -> Zeroizing { + Zeroizing(value) + } +} + +impl ops::Deref for Zeroizing +where + Z: Zeroize, +{ + type Target = Z; + + fn deref(&self) -> &Z { + &self.0 + } +} + +impl ops::DerefMut for Zeroizing +where + Z: Zeroize, +{ + fn deref_mut(&mut self) -> &mut Z { + &mut self.0 + } +} + +impl Zeroize for Zeroizing +where + Z: Zeroize, +{ + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl ZeroizeOnDrop for Zeroizing where Z: Zeroize {} + +impl Drop for Zeroizing +where + Z: Zeroize, +{ + fn drop(&mut self) { + self.0.zeroize() + } +} + +/// Use fences to prevent accesses from being reordered before this +/// point, which should hopefully help ensure that all accessors +/// see zeroes after this point. +#[inline] +fn atomic_fence() { + atomic::compiler_fence(atomic::Ordering::SeqCst); +} + +/// Perform a volatile write to the destination +#[inline] +fn volatile_write(dst: &mut T, src: T) { + unsafe { ptr::write_volatile(dst, src) } +} + +/// Perform a volatile `memset` operation which fills a slice with a value +/// +/// Safety: +/// The memory pointed to by `dst` must be a single allocated object that is valid for `count` +/// contiguous elements of `T`. +/// `count` must not be larger than an `isize`. +/// `dst` being offset by `mem::size_of:: * count` bytes must not wrap around the address space. +/// Also `dst` must be properly aligned. +#[inline] +unsafe fn volatile_set(dst: *mut T, src: T, count: usize) { + // TODO(tarcieri): use `volatile_set_memory` when stabilized + for i in 0..count { + // Safety: + // + // This is safe because there is room for at least `count` objects of type `T` in the + // allocation pointed to by `dst`, because `count <= isize::MAX` and because + // `dst.add(count)` must not wrap around the address space. + let ptr = dst.add(i); + // Safety: + // + // This is safe, because the pointer is valid and because `dst` is well aligned for `T` and + // `ptr` is an offset of `dst` by a multiple of `mem::size_of::()` bytes. + ptr::write_volatile(ptr, src); + } +} + +/// `PhantomData` is always zero sized so provide a Zeroize implementation. +impl Zeroize for PhantomData { + fn zeroize(&mut self) {} +} +/// `PhantomData` is always zero sized so provide a ZeroizeOnDrop implementation. +impl ZeroizeOnDrop for PhantomData {} +/// `PhantomPinned` is zero sized so provide a Zeroize implementation. +impl Zeroize for PhantomPinned { + fn zeroize(&mut self) {} +} +/// `PhantomPinned` is zero sized so provide a ZeroizeOnDrop implementation. +impl ZeroizeOnDrop for PhantomPinned {} +/// `()` is zero sized so provide a Zeroize implementation. +impl Zeroize for () { + fn zeroize(&mut self) {} +} +/// `()` is zero sized so provide a ZeroizeOnDrop implementation. +impl ZeroizeOnDrop for () {} + +/// Generic implementation of Zeroize for tuples up to 10 parameters. +impl Zeroize for (A,) { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} +/// Generic implementation of ZeroizeOnDrop for tuples up to 10 parameters. +impl ZeroizeOnDrop for (A,) {} +macro_rules! impl_zeroize_tuple { + ( $( $type_name:ident )+ ) => { + impl<$($type_name: Zeroize),+> Zeroize for ($($type_name),+) { + fn zeroize(&mut self) { + #[allow(non_snake_case)] + let ($($type_name),+) = self; + $($type_name.zeroize());+ + } + } + + impl<$($type_name: ZeroizeOnDrop),+> ZeroizeOnDrop for ($($type_name),+) { } + } +} +// Generic implementations for tuples up to 10 parameters. +impl_zeroize_tuple! { A B } +impl_zeroize_tuple! { A B C } +impl_zeroize_tuple! { A B C D } +impl_zeroize_tuple! { A B C D E } +impl_zeroize_tuple! { A B C D E F } +impl_zeroize_tuple! { A B C D E F G } +impl_zeroize_tuple! { A B C D E F G H } +impl_zeroize_tuple! { A B C D E F G H I } +impl_zeroize_tuple! { A B C D E F G H I L } + +#[cfg(test)] +mod tests { + use super::*; + + use core::mem::size_of; + + #[cfg(feature = "alloc")] + use alloc::boxed::Box; + + #[derive(Clone, Debug, PartialEq)] + struct ZeroizedOnDrop(u64); + + impl Drop for ZeroizedOnDrop { + fn drop(&mut self) { + self.0.zeroize(); + } + } + + #[test] + fn non_zero() { + macro_rules! non_zero_test { + ($($type:ty),+) => { + $(let mut value = <$type>::new(42).unwrap(); + value.zeroize(); + assert_eq!(value.get(), 1);)+ + }; + } + + non_zero_test!( + NonZeroI8, + NonZeroI16, + NonZeroI32, + NonZeroI64, + NonZeroI128, + NonZeroIsize + ); + non_zero_test!( + NonZeroU8, + NonZeroU16, + NonZeroU32, + NonZeroU64, + NonZeroU128, + NonZeroUsize + ); + } + + #[test] + fn zeroize_byte_arrays() { + let mut arr = [42u8; 137]; + arr.zeroize(); + assert_eq!(arr.as_ref(), [0u8; 137].as_ref()); + } + + #[test] + fn zeroize_on_drop_byte_arrays() { + let mut arr = [ZeroizedOnDrop(42); 1]; + unsafe { core::ptr::drop_in_place(&mut arr) }; + assert_eq!(arr.as_ref(), [ZeroizedOnDrop(0); 1].as_ref()); + } + + #[test] + fn zeroize_maybeuninit_byte_arrays() { + let mut arr = [MaybeUninit::new(42u64); 64]; + arr.zeroize(); + let arr_init: [u64; 64] = unsafe { core::mem::transmute(arr) }; + assert_eq!(arr_init, [0u64; 64]); + } + + #[test] + fn zeroize_check_zerosize_types() { + // Since we assume these types have zero size, we test this holds for + // the current version of Rust. + assert_eq!(size_of::<()>(), 0); + assert_eq!(size_of::(), 0); + assert_eq!(size_of::>(), 0); + } + + #[test] + fn zeroize_check_tuple() { + let mut tup1 = (42u8,); + tup1.zeroize(); + assert_eq!(tup1, (0u8,)); + + let mut tup2 = (42u8, 42u8); + tup2.zeroize(); + assert_eq!(tup2, (0u8, 0u8)); + } + + #[test] + fn zeroize_on_drop_check_tuple() { + let mut tup1 = (ZeroizedOnDrop(42),); + unsafe { core::ptr::drop_in_place(&mut tup1) }; + assert_eq!(tup1, (ZeroizedOnDrop(0),)); + + let mut tup2 = (ZeroizedOnDrop(42), ZeroizedOnDrop(42)); + unsafe { core::ptr::drop_in_place(&mut tup2) }; + assert_eq!(tup2, (ZeroizedOnDrop(0), ZeroizedOnDrop(0))); + } + + #[cfg(feature = "alloc")] + #[test] + fn zeroize_vec() { + let mut vec = vec![42; 3]; + vec.zeroize(); + assert!(vec.is_empty()); + } + + #[cfg(feature = "alloc")] + #[test] + fn zeroize_vec_entire_capacity() { + #[derive(Clone)] + struct PanicOnNonZeroDrop(u64); + + impl Zeroize for PanicOnNonZeroDrop { + fn zeroize(&mut self) { + self.0 = 0; + } + } + + impl Drop for PanicOnNonZeroDrop { + fn drop(&mut self) { + if self.0 != 0 { + panic!("dropped non-zeroized data"); + } + } + } + + // Ensure that the entire capacity of the vec is zeroized and that no unitinialized data + // is ever interpreted as initialized + let mut vec = vec![PanicOnNonZeroDrop(42); 2]; + + unsafe { + vec.set_len(1); + } + + vec.zeroize(); + + unsafe { + vec.set_len(2); + } + + drop(vec); + } + + #[cfg(feature = "alloc")] + #[test] + fn zeroize_string() { + let mut string = String::from("Hello, world!"); + string.zeroize(); + assert!(string.is_empty()); + } + + #[cfg(feature = "alloc")] + #[test] + fn zeroize_string_entire_capacity() { + let mut string = String::from("Hello, world!"); + string.truncate(5); + + string.zeroize(); + + // convert the string to a vec to easily access the unused capacity + let mut as_vec = string.into_bytes(); + unsafe { as_vec.set_len(as_vec.capacity()) }; + + assert!(as_vec.iter().all(|byte| *byte == 0)); + } + + #[cfg(feature = "alloc")] + #[test] + fn zeroize_box() { + let mut boxed_arr = Box::new([42u8; 3]); + boxed_arr.zeroize(); + assert_eq!(boxed_arr.as_ref(), &[0u8; 3]); + } +} diff --git a/zeroize/src/x86.rs b/zeroize/src/x86.rs new file mode 100644 index 00000000..a66cf36c --- /dev/null +++ b/zeroize/src/x86.rs @@ -0,0 +1,40 @@ +//! [`Zeroize`] impls for x86 SIMD registers + +use crate::{atomic_fence, volatile_write, Zeroize}; + +#[cfg(target_arch = "x86")] +use core::arch::x86::*; + +#[cfg(target_arch = "x86_64")] +use core::arch::x86_64::*; + +macro_rules! impl_zeroize_for_simd_register { + ($type:ty, $feature:expr, $zero_value:ident) => { + #[cfg_attr(docsrs, doc(cfg(target_arch = "x86")))] // also `x86_64` + #[cfg_attr(docsrs, doc(cfg(target_feature = $feature)))] + impl Zeroize for $type { + fn zeroize(&mut self) { + volatile_write(self, unsafe { $zero_value() }); + atomic_fence(); + } + } + }; +} + +#[cfg(target_feature = "sse")] +impl_zeroize_for_simd_register!(__m128, "sse", _mm_setzero_ps); + +#[cfg(target_feature = "sse2")] +impl_zeroize_for_simd_register!(__m128d, "sse2", _mm_setzero_pd); + +#[cfg(target_feature = "sse2")] +impl_zeroize_for_simd_register!(__m128i, "sse2", _mm_setzero_si128); + +#[cfg(target_feature = "avx")] +impl_zeroize_for_simd_register!(__m256, "avx", _mm256_setzero_ps); + +#[cfg(target_feature = "avx")] +impl_zeroize_for_simd_register!(__m256d, "avx", _mm256_setzero_pd); + +#[cfg(target_feature = "avx")] +impl_zeroize_for_simd_register!(__m256i, "avx", _mm256_setzero_si256); diff --git a/zeroize/tests/zeroize_derive.rs b/zeroize/tests/zeroize_derive.rs new file mode 100644 index 00000000..4e0fdc61 --- /dev/null +++ b/zeroize/tests/zeroize_derive.rs @@ -0,0 +1,317 @@ +//! Integration tests for `zeroize_derive` proc macros +#![cfg(feature = "zeroize_derive")] + +use zeroize::{Zeroize, ZeroizeOnDrop}; + +#[test] +fn derive_tuple_struct_test() { + #[derive(Zeroize, ZeroizeOnDrop)] + struct Z([u8; 3]); + + let mut value = Z([1, 2, 3]); + value.zeroize(); + assert_eq!(&value.0, &[0, 0, 0]) +} + +#[test] +#[cfg(feature = "alloc")] +fn derive_struct_test() { + #[derive(Zeroize, ZeroizeOnDrop)] + struct Z { + string: String, + vec: Vec, + bytearray: [u8; 3], + number: usize, + boolean: bool, + } + + let mut value = Z { + string: String::from("Hello, world!"), + vec: vec![1, 2, 3], + bytearray: [4, 5, 6], + number: 42, + boolean: true, + }; + + value.zeroize(); + + assert!(value.string.is_empty()); + assert!(value.vec.is_empty()); + assert_eq!(&value.bytearray, &[0, 0, 0]); + assert_eq!(value.number, 0); + assert!(!value.boolean); +} + +#[test] +fn derive_enum_test() { + #[derive(Zeroize, ZeroizeOnDrop)] + enum Z { + #[allow(dead_code)] + Variant1, + Variant2(usize), + } + + let mut value = Z::Variant2(26); + + value.zeroize(); + + assert!(matches!(value, Z::Variant2(0))); +} + +/// Test that the custom macro actually derived `Drop` for `Z` +#[test] +fn derive_struct_drop() { + #[derive(Zeroize, ZeroizeOnDrop)] + struct Z([u8; 3]); + + assert!(std::mem::needs_drop::()); +} + +/// Test that the custom macro actually derived `Drop` for `Z` +#[test] +fn derive_enum_drop() { + #[allow(dead_code)] + #[derive(Zeroize, ZeroizeOnDrop)] + enum Z { + Variant1, + Variant2(usize), + } + + assert!(std::mem::needs_drop::()); +} + +/// Test that the custom macro actually derived `Drop` for `Z` +#[test] +fn derive_struct_only_drop() { + #[derive(ZeroizeOnDrop)] + struct Z([u8; 3]); + + assert!(std::mem::needs_drop::()); +} + +/// Test that the custom macro actually derived `Drop` for `Z` +#[test] +fn derive_enum_only_drop() { + #[allow(dead_code)] + #[derive(ZeroizeOnDrop)] + enum Z { + Variant1, + Variant2(usize), + } + + assert!(std::mem::needs_drop::()); +} + +/// Test that `Drop` is not derived in the following case by defining a +/// `Drop` impl which should conflict if the custom derive defined one too +#[allow(dead_code)] +#[derive(Zeroize)] +struct ZeroizeNoDropStruct([u8; 3]); + +impl Drop for ZeroizeNoDropStruct { + fn drop(&mut self) {} +} + +#[allow(dead_code)] +#[derive(Zeroize)] +enum ZeroizeNoDropEnum { + Variant([u8; 3]), +} + +impl Drop for ZeroizeNoDropEnum { + fn drop(&mut self) {} +} + +#[test] +#[cfg(feature = "alloc")] +fn derive_struct_skip() { + #[derive(Zeroize, ZeroizeOnDrop)] + struct Z { + string: String, + vec: Vec, + #[zeroize(skip)] + bytearray: [u8; 3], + number: usize, + boolean: bool, + } + + let mut value = Z { + string: String::from("Hello, world!"), + vec: vec![1, 2, 3], + bytearray: [4, 5, 6], + number: 42, + boolean: true, + }; + + value.zeroize(); + + assert!(value.string.is_empty()); + assert!(value.vec.is_empty()); + assert_eq!(&value.bytearray, &[4, 5, 6]); + assert_eq!(value.number, 0); + assert!(!value.boolean); +} + +#[test] +#[cfg(feature = "alloc")] +fn derive_enum_skip() { + #[derive(Zeroize, ZeroizeOnDrop)] + enum Z { + #[allow(dead_code)] + Variant1, + #[zeroize(skip)] + Variant2([u8; 3]), + #[zeroize(skip)] + Variant3 { + string: String, + vec: Vec, + bytearray: [u8; 3], + number: usize, + boolean: bool, + }, + Variant4 { + string: String, + vec: Vec, + #[zeroize(skip)] + bytearray: [u8; 3], + number: usize, + boolean: bool, + }, + } + + let mut value = Z::Variant2([4, 5, 6]); + + value.zeroize(); + + assert!(matches!(&value, Z::Variant2([4, 5, 6]))); + + let mut value = Z::Variant3 { + string: String::from("Hello, world!"), + vec: vec![1, 2, 3], + bytearray: [4, 5, 6], + number: 42, + boolean: true, + }; + + value.zeroize(); + + assert!(matches!( + &value, + Z::Variant3 { string, vec, bytearray, number, boolean } + if string == "Hello, world!" && + vec == &[1, 2, 3] && + bytearray == &[4, 5, 6] && + *number == 42 && + *boolean + )); + + let mut value = Z::Variant4 { + string: String::from("Hello, world!"), + vec: vec![1, 2, 3], + bytearray: [4, 5, 6], + number: 42, + boolean: true, + }; + + value.zeroize(); + + assert!(matches!( + &value, + Z::Variant4 { string, vec, bytearray, number, boolean } + if string.is_empty() && + vec.is_empty() && + bytearray == &[4, 5, 6] && + *number == 0 && + !boolean + )); +} + +#[test] +fn derive_bound() { + trait T: Zeroize {} + + impl T for u8 {} + + #[derive(Zeroize)] + #[zeroize(bound = "X: T")] + struct Z(X); + + let mut value = Z(5_u8); + + value.zeroize(); + + assert_eq!(value.0, 0); +} + +#[test] +fn derive_inherit_zeroize_on_drop() { + #[derive(ZeroizeOnDrop)] + struct X([u8; 3]); + + #[derive(ZeroizeOnDrop)] + struct Z(X); + + let mut value = Z(X([1, 2, 3])); + unsafe { + std::ptr::drop_in_place(&mut value); + } + assert_eq!(&value.0 .0, &[0, 0, 0]) +} + +#[test] +fn derive_inherit_from_both() { + #[derive(Zeroize, ZeroizeOnDrop)] + struct X([u8; 3]); + + #[derive(ZeroizeOnDrop)] + struct Z(X); + + let mut value = Z(X([1, 2, 3])); + unsafe { + std::ptr::drop_in_place(&mut value); + } + assert_eq!(&value.0 .0, &[0, 0, 0]) +} + +#[test] +fn derive_inherit_both() { + #[derive(Zeroize, ZeroizeOnDrop)] + struct X([u8; 3]); + + #[derive(Zeroize, ZeroizeOnDrop)] + struct Z(X); + + let mut value = Z(X([1, 2, 3])); + unsafe { + std::ptr::drop_in_place(&mut value); + } + assert_eq!(&value.0 .0, &[0, 0, 0]) +} + +#[test] +fn derive_deref() { + struct X([u8; 3]); + + impl std::ops::Deref for X { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl std::ops::DerefMut for X { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + #[derive(Zeroize, ZeroizeOnDrop)] + struct Z(X); + + let mut value = Z(X([1, 2, 3])); + unsafe { + std::ptr::drop_in_place(&mut value); + } + assert_eq!(&value.0 .0, &[0, 0, 0]) +}