completely rewrited
This commit is contained in:
parent
2e633283aa
commit
4d5fd2b852
16 changed files with 840 additions and 632 deletions
162
.github/workflows/rust.yml
vendored
Normal file
162
.github/workflows/rust.yml
vendored
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
name: Rust
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
tags: [ 'v*' ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
APP_NAME: jsonwebkey-cli
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Short tag
|
||||
id: short_tag
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
run: echo "::set-output name=tag::$(basename ${{ github.ref }})"
|
||||
- name: Hash
|
||||
id: hash
|
||||
if: ${{ startsWith(github.ref, 'refs/heads/') }}
|
||||
run: echo "::set-output name=tag::${{ github.sha }}"
|
||||
outputs:
|
||||
tag: ${{ steps.short_tag.outputs.tag }}${{ steps.hash.outputs.tag }}
|
||||
|
||||
build:
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
needs: tag
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- os: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
test: true
|
||||
cross: false
|
||||
- os: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
test: true
|
||||
cross: false
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
test: true
|
||||
cross: false
|
||||
- os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
test: true
|
||||
cross: false
|
||||
steps:
|
||||
- name: Git config
|
||||
if: ${{ matrix.config.os == 'windows-latest' }}
|
||||
run: git config --global core.autocrlf input
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install musl tools
|
||||
if: ${{ matrix.config.target == 'x86_64-unknown-linux-musl' }}
|
||||
run: sudo apt-get install musl-tools musl-dev
|
||||
- name: Install rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.config.target }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-cargo
|
||||
with:
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/*.crate') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
- name: Cache target
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-target
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.config.target }}-${{ hashFiles('**/*.o') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.config.target }}
|
||||
- name: Build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.config.cross }}
|
||||
command: build
|
||||
args: --release --target ${{ matrix.config.target }} --all-features
|
||||
- name: Test
|
||||
if: ${{ matrix.config.test }}
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --release --target ${{ matrix.config.target }} --all-features
|
||||
- name: Create release zip for UNIX
|
||||
if: ${{ matrix.config.os != 'windows-latest' }}
|
||||
run: |
|
||||
mkdir -p ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}
|
||||
cp target/${{ matrix.config.target }}/release/${{ env.APP_NAME }} ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}/
|
||||
cp README.md ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}/
|
||||
cp LICENSE ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}/
|
||||
zip -r ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}.zip ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}/
|
||||
- name: Create release zip for Windows
|
||||
if: ${{ matrix.config.os == 'windows-latest' }}
|
||||
run: |
|
||||
mkdir ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}
|
||||
cp target/${{ matrix.config.target }}/release/${{ env.APP_NAME }}.exe ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}/
|
||||
cp README.md ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}/
|
||||
cp LICENSE ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}/
|
||||
Compress-Archive -DestinationPath ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}.zip -Path ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}/
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag}}.zip
|
||||
path: |
|
||||
./${{ env.APP_NAME }}-${{ matrix.config.target }}-${{ needs.tag.outputs.tag }}.zip
|
||||
|
||||
release:
|
||||
needs: [build, tag]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
steps:
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ needs.tag.outputs.tag }}
|
||||
draft: false
|
||||
prerelease: false
|
||||
outputs:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
|
||||
upload:
|
||||
needs: [release, tag]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-pc-windows-msvc
|
||||
- x86_64-apple-darwin
|
||||
- x86_64-unknown-linux-gnu
|
||||
- x86_64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: ${{ env.APP_NAME }}-${{ matrix.target }}-${{ needs.tag.outputs.tag }}.zip
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
|
||||
asset_path: ./${{ env.APP_NAME }}-${{ matrix.target }}-${{ needs.tag.outputs.tag }}.zip
|
||||
asset_name: ${{ env.APP_NAME }}-${{ matrix.target }}-${{ needs.tag.outputs.tag }}.zip
|
||||
asset_content_type: application/zip
|
||||
134
.travis.yml
134
.travis.yml
|
|
@ -1,134 +0,0 @@
|
|||
# Based on the "trust" template v0.1.2
|
||||
# https://github.com/japaric/trust/tree/v0.1.2
|
||||
|
||||
dist: trusty
|
||||
language: rust
|
||||
services: docker
|
||||
sudo: required
|
||||
|
||||
# TODO Rust builds on stable by default, this can be
|
||||
# overridden on a case by case basis down below.
|
||||
|
||||
env:
|
||||
global:
|
||||
# TODO Update this to match the name of your project.
|
||||
- CRATE_NAME=jsonwebkey-cli
|
||||
|
||||
matrix:
|
||||
# TODO These are all the build jobs. Adjust as necessary. Comment out what you
|
||||
# don't need
|
||||
include:
|
||||
# # Android
|
||||
# - env: TARGET=aarch64-linux-android DISABLE_TESTS=1
|
||||
# - env: TARGET=arm-linux-androideabi DISABLE_TESTS=1
|
||||
# - env: TARGET=armv7-linux-androideabi DISABLE_TESTS=1
|
||||
# - env: TARGET=i686-linux-android DISABLE_TESTS=1
|
||||
# - env: TARGET=x86_64-linux-android DISABLE_TESTS=1
|
||||
|
||||
# # iOS
|
||||
# - env: TARGET=aarch64-apple-ios DISABLE_TESTS=1
|
||||
# os: osx
|
||||
# - env: TARGET=armv7-apple-ios DISABLE_TESTS=1
|
||||
# os: osx
|
||||
# - env: TARGET=armv7s-apple-ios DISABLE_TESTS=1
|
||||
# os: osx
|
||||
# - env: TARGET=i386-apple-ios DISABLE_TESTS=1
|
||||
# os: osx
|
||||
# - env: TARGET=x86_64-apple-ios DISABLE_TESTS=1
|
||||
# os: osx
|
||||
|
||||
# Linux
|
||||
- env: TARGET=aarch64-unknown-linux-gnu
|
||||
# - env: TARGET=arm-unknown-linux-gnueabi
|
||||
- env: TARGET=armv7-unknown-linux-gnueabihf
|
||||
# - env: TARGET=i686-unknown-linux-gnu
|
||||
# - env: TARGET=i686-unknown-linux-musl
|
||||
# - env: TARGET=mips-unknown-linux-gnu
|
||||
# - env: TARGET=mips64-unknown-linux-gnuabi64
|
||||
# - env: TARGET=mips64el-unknown-linux-gnuabi64
|
||||
# - env: TARGET=mipsel-unknown-linux-gnu
|
||||
# - env: TARGET=powerpc-unknown-linux-gnu
|
||||
# - env: TARGET=powerpc64-unknown-linux-gnu
|
||||
# - env: TARGET=powerpc64le-unknown-linux-gnu
|
||||
# - env: TARGET=s390x-unknown-linux-gnu DISABLE_TESTS=1
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
- env: TARGET=x86_64-unknown-linux-musl
|
||||
|
||||
# OSX
|
||||
# - env: TARGET=i686-apple-darwin
|
||||
# os: osx
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
|
||||
# # *BSD
|
||||
# - env: TARGET=i686-unknown-freebsd DISABLE_TESTS=1
|
||||
# - env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1
|
||||
# - env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1
|
||||
|
||||
# # Windows
|
||||
# - env: TARGET=x86_64-pc-windows-gnu
|
||||
|
||||
# Bare metal
|
||||
# These targets don't support std and as such are likely not suitable for
|
||||
# most crates.
|
||||
# - env: TARGET=thumbv6m-none-eabi
|
||||
# - env: TARGET=thumbv7em-none-eabi
|
||||
# - env: TARGET=thumbv7em-none-eabihf
|
||||
# - env: TARGET=thumbv7m-none-eabi
|
||||
|
||||
# Testing other channels
|
||||
- env: TARGET=x86_64-unknown-linux-gnu
|
||||
rust: nightly
|
||||
- env: TARGET=x86_64-apple-darwin
|
||||
os: osx
|
||||
rust: nightly
|
||||
|
||||
before_install:
|
||||
- set -e
|
||||
- rustup self update
|
||||
|
||||
install:
|
||||
- sh ci/install.sh
|
||||
- source ~/.cargo/env || true
|
||||
|
||||
script:
|
||||
- bash ci/script.sh
|
||||
|
||||
after_script: set +e
|
||||
|
||||
before_deploy:
|
||||
- sh ci/before_deploy.sh
|
||||
|
||||
deploy:
|
||||
# TODO update `api_key.secure`
|
||||
# - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new
|
||||
# - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789
|
||||
# - Paste the output down here
|
||||
api_key:
|
||||
secure: PDNDn9rAd2VpYwvC3gtpTYlEsEZA1YY6ApNstZLLX1iG7uMGe0dIn/w14sfsjjyShb9lM/kznRx3chfPu6HBM5S9Nn3MP0RXsNfAsQ/QzkOfUI5C5IwgQIW+qblKFWQ2pnFG0rE8O7A9nvJbnh1HdFLqoiXWFLIadj0BC11gjUgJdCIFbCDrJjA7lp2vZ4GRt3BrTl2g35ZtfH8bTf/G9C2gTweLPvRF55VD3XjUqk0NfwwrZk4Z/8tARzRtiXeLYQe6sets9dcwn6hpEtLlmD1UjIcxTCHmLNC94FVh1NR0X5wC7tKCkpGU6SvHVvB49QHBHTbCyLy84HaM8qKpOipBz1JrG2+f2qm8kEkluwNPTW+QKRzJsAI6B42ZO+DeEmON1RxanSrNRF4k9USggWTTq0yp2pAGW/dsBWmMa+XHO75xeDLF5eTqc7tfkTf+E9x1ZX03CdF8G3VEvRViPBiaDOd9UUUNn5VeXlCUGvEMD9TjOvSYb3yiLBfQydaRWyxF5Wj1l3G6wDerQX9d28a4yHLZy+n2eFf+syewy+DVithSkZj87pqellHP6bcO5fGkQr/sy2hBM24u7JuFP+KNACRmO+463N/Lc3gBmeqNPIVnXObXyZKxQseCvdyXw+JW8ooxQhKORjEVFrqInclfiizFy9LHKUZo1o5lti0=
|
||||
file_glob: true
|
||||
file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.*
|
||||
on:
|
||||
# TODO Here you can pick which targets will generate binary releases
|
||||
# In this example, there are some targets that are tested using the stable
|
||||
# and nightly channels. This condition makes sure there is only one release
|
||||
# for such targets and that's generated using the stable channel
|
||||
condition: $TRAVIS_RUST_VERSION = stable
|
||||
tags: true
|
||||
provider: releases
|
||||
skip_cleanup: true
|
||||
|
||||
cache: cargo
|
||||
before_cache:
|
||||
# Travis can't cache files that are not readable by "others"
|
||||
- chmod -R a+r $HOME/.cargo
|
||||
|
||||
branches:
|
||||
only:
|
||||
# release tags
|
||||
- /^v\d+\.\d+\.\d+.*$/
|
||||
- master
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
89
appveyor.yml
89
appveyor.yml
|
|
@ -1,89 +0,0 @@
|
|||
# Based on the "trust" template v0.1.2
|
||||
# https://github.com/japaric/trust/tree/v0.1.2
|
||||
|
||||
environment:
|
||||
global:
|
||||
# TODO This is the Rust channel that build jobs will use by default but can be
|
||||
# overridden on a case by case basis down below
|
||||
RUST_VERSION: stable
|
||||
|
||||
# TODO Update this to match the name of your project.
|
||||
CRATE_NAME: jsonwebkey-cli
|
||||
|
||||
# TODO These are all the build jobs. Adjust as necessary. Comment out what you
|
||||
# don't need
|
||||
matrix:
|
||||
# # MinGW
|
||||
# - TARGET: i686-pc-windows-gnu
|
||||
# - TARGET: x86_64-pc-windows-gnu
|
||||
|
||||
# MSVC
|
||||
- TARGET: i686-pc-windows-msvc
|
||||
- TARGET: x86_64-pc-windows-msvc
|
||||
|
||||
# # Testing other channels
|
||||
# - TARGET: x86_64-pc-windows-gnu
|
||||
# RUST_VERSION: nightly
|
||||
# - TARGET: x86_64-pc-windows-msvc
|
||||
# RUST_VERSION: nightly
|
||||
|
||||
install:
|
||||
- ps: >-
|
||||
If ($env:TARGET -eq 'x86_64-pc-windows-gnu') {
|
||||
$env:PATH += ';C:\msys64\mingw64\bin'
|
||||
} ElseIf ($env:TARGET -eq 'i686-pc-windows-gnu') {
|
||||
$env:PATH += ';C:\msys64\mingw32\bin'
|
||||
}
|
||||
- curl -sSf -o rustup-init.exe https://win.rustup.rs/
|
||||
- rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_VERSION%
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- rustc -Vv
|
||||
- cargo -V
|
||||
|
||||
# TODO This is the "test phase", tweak it as you see fit
|
||||
test_script:
|
||||
# we don't run the "test phase" when doing deploys
|
||||
- if [%APPVEYOR_REPO_TAG%]==[false] (
|
||||
cargo build --target %TARGET% --release &&
|
||||
cargo test --target %TARGET% --release
|
||||
)
|
||||
|
||||
before_deploy:
|
||||
# TODO Update this to build the artifacts that matter to you
|
||||
- cargo build --target %TARGET% --release
|
||||
- ps: ci\before_deploy.ps1
|
||||
|
||||
deploy:
|
||||
artifact: /.*\.zip/
|
||||
# TODO update `auth_token.secure`
|
||||
# - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new
|
||||
# - Encrypt it. Go to https://ci.appveyor.com/tools/encrypt
|
||||
# - Paste the output down here
|
||||
auth_token:
|
||||
secure: gGVqlnI+qVprHvmNiWexLNVc/xnfb+ghKSGW+slFF+4pxLl3jTdlO4LpzpM4n6no
|
||||
description: ''
|
||||
on:
|
||||
# TODO Here you can pick which targets will generate binary releases
|
||||
# In this example, there are some targets that are tested using the stable
|
||||
# and nightly channels. This condition makes sure there is only one release
|
||||
# for such targets and that's generated using the stable channel
|
||||
RUST_VERSION: stable
|
||||
appveyor_repo_tag: true
|
||||
provider: GitHub
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.cargo\registry
|
||||
- target
|
||||
|
||||
branches:
|
||||
only:
|
||||
# Release tags
|
||||
- /^v\d+\.\d+\.\d+.*$/
|
||||
- master
|
||||
|
||||
# notifications:
|
||||
# - provider: Email
|
||||
# on_build_success: false
|
||||
|
||||
# Building is done in the test phase, so we disable Appveyor's build phase.
|
||||
build: false
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# This script takes care of packaging the build artifacts that will go in the
|
||||
# release zipfile
|
||||
|
||||
$SRC_DIR = $PWD.Path
|
||||
$STAGE = [System.Guid]::NewGuid().ToString()
|
||||
|
||||
Set-Location $ENV:Temp
|
||||
New-Item -Type Directory -Name $STAGE
|
||||
Set-Location $STAGE
|
||||
|
||||
$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip"
|
||||
|
||||
# TODO Update this to package the right artifacts
|
||||
Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\jsonwebkey-cli.exe" '.\'
|
||||
Copy-Item "$SRC_DIR\README.md" '.\'
|
||||
Copy-Item "$SRC_DIR\LICENSE" '.\LICENSE'
|
||||
|
||||
7z a "$ZIP" *
|
||||
|
||||
Push-AppveyorArtifact "$ZIP"
|
||||
|
||||
Remove-Item *.* -Force
|
||||
Set-Location ..
|
||||
Remove-Item $STAGE
|
||||
Set-Location $SRC_DIR
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
# This script takes care of building your crate and packaging it for release
|
||||
|
||||
set -ex
|
||||
|
||||
main() {
|
||||
local src=$(pwd) \
|
||||
stage=
|
||||
|
||||
case $TRAVIS_OS_NAME in
|
||||
linux)
|
||||
stage=$(mktemp -d)
|
||||
;;
|
||||
osx)
|
||||
stage=$(mktemp -d -t tmp)
|
||||
;;
|
||||
esac
|
||||
|
||||
test -f Cargo.lock || cargo generate-lockfile
|
||||
|
||||
cross build --target $TARGET --release
|
||||
|
||||
# TODO Update this to package the right artifacts
|
||||
cp target/$TARGET/release/jsonwebkey-cli $stage/
|
||||
cp README.md $stage/
|
||||
cp LICENSE $stage/LICENSE
|
||||
|
||||
cd $stage
|
||||
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
|
||||
cd $src
|
||||
|
||||
rm -rf $stage
|
||||
}
|
||||
|
||||
main
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
set -ex
|
||||
|
||||
main() {
|
||||
local target=
|
||||
if [ $TRAVIS_OS_NAME = linux ]; then
|
||||
target=x86_64-unknown-linux-musl
|
||||
sort=sort
|
||||
else
|
||||
target=x86_64-apple-darwin
|
||||
sort=gsort # for `sort --sort-version`, from brew's coreutils.
|
||||
fi
|
||||
|
||||
# Builds for iOS are done on OSX, but require the specific target to be
|
||||
# installed.
|
||||
case $TARGET in
|
||||
aarch64-apple-ios)
|
||||
rustup target install aarch64-apple-ios
|
||||
;;
|
||||
armv7-apple-ios)
|
||||
rustup target install armv7-apple-ios
|
||||
;;
|
||||
armv7s-apple-ios)
|
||||
rustup target install armv7s-apple-ios
|
||||
;;
|
||||
i386-apple-ios)
|
||||
rustup target install i386-apple-ios
|
||||
;;
|
||||
x86_64-apple-ios)
|
||||
rustup target install x86_64-apple-ios
|
||||
;;
|
||||
esac
|
||||
|
||||
# This fetches latest stable release
|
||||
local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \
|
||||
| cut -d/ -f3 \
|
||||
| grep -E '^v[0.1.0-9.]+$' \
|
||||
| $sort --version-sort \
|
||||
| tail -n1)
|
||||
curl -LSfs https://japaric.github.io/trust/install.sh | \
|
||||
sh -s -- \
|
||||
--force \
|
||||
--git japaric/cross \
|
||||
--tag $tag \
|
||||
--target $target
|
||||
}
|
||||
|
||||
main
|
||||
24
ci/script.sh
24
ci/script.sh
|
|
@ -1,24 +0,0 @@
|
|||
# This script takes care of testing your crate
|
||||
|
||||
set -ex
|
||||
|
||||
# TODO This is the "test phase", tweak it as you see fit
|
||||
main() {
|
||||
#cross build --target $TARGET
|
||||
cross build --target $TARGET --release
|
||||
|
||||
if [ ! -z $DISABLE_TESTS ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
#cross test --target $TARGET
|
||||
cross test --target $TARGET --release
|
||||
|
||||
#cross run --target $TARGET
|
||||
#cross run --target $TARGET --release
|
||||
}
|
||||
|
||||
# we don't run the "test phase" when doing deploys
|
||||
if [ -z $TRAVIS_TAG ]; then
|
||||
main
|
||||
fi
|
||||
|
|
@ -14,5 +14,6 @@ categories = ["authentication"]
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
jsonwebkey-convert = {version = "0.3", path = "../jsonwebkey-convert"}
|
||||
jsonwebkey-convert = {version = "0.3", path = "../jsonwebkey-convert", features = ["pem_support"]}
|
||||
clap = "2"
|
||||
serde_json = "1"
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
use clap::{crate_authors, crate_version, App, AppSettings, Arg, SubCommand};
|
||||
use jsonwebkey_convert::der::*;
|
||||
use jsonwebkey_convert::*;
|
||||
use std::fs;
|
||||
|
||||
fn main() -> Result<(), JWKConvertError> {
|
||||
fn main() -> Result<(), Error> {
|
||||
let matches = App::new("Json Web Key CLI")
|
||||
.version(crate_version!())
|
||||
.author(crate_authors!())
|
||||
|
|
@ -62,7 +63,8 @@ fn main() -> Result<(), JWKConvertError> {
|
|||
let jwk_path = jwk_to_pem.value_of("jwk").unwrap();
|
||||
let output_path = jwk_to_pem.value_of("output").unwrap();
|
||||
let data = fs::read(jwk_path).unwrap();
|
||||
let pem = load_jwk(&data)?.pubkey.to_pem()?;
|
||||
let jwk: RSAPublicKey = serde_json::from_slice(&data[..])?;
|
||||
let pem = jwk.to_pem()?;
|
||||
fs::write(output_path, &pem).unwrap();
|
||||
} else if let Some(pem_to_jwk) = matches.subcommand_matches("pem-to-jwk") {
|
||||
let pem_path = pem_to_jwk.value_of("pem").unwrap();
|
||||
|
|
@ -70,13 +72,18 @@ fn main() -> Result<(), JWKConvertError> {
|
|||
let kid = pem_to_jwk.value_of("kid").map(|x| x.to_string());
|
||||
let jwk_use = pem_to_jwk.value_of("use").map(|x| x.to_string());
|
||||
let data = fs::read(pem_path).unwrap();
|
||||
let jwk = RSAJWK {
|
||||
pubkey: load_pem(&data)?,
|
||||
kid,
|
||||
jwk_use,
|
||||
}
|
||||
.to_jwk()?;
|
||||
fs::write(output_path, &jwk).unwrap();
|
||||
let mut jwk = RSAPublicKey::from_pem(&data)?;
|
||||
jwk.generic.kid = kid;
|
||||
jwk.generic.use_ = jwk_use.map(|x| match x.as_str() {
|
||||
"enc" => KeyUse::Encryption,
|
||||
"sig" => KeyUse::Signature,
|
||||
_ => {
|
||||
eprintln!("Unknown key use: {}", x);
|
||||
KeyUse::Encryption
|
||||
}
|
||||
});
|
||||
let jwk_bytes = serde_json::to_vec(&jwk)?;
|
||||
fs::write(output_path, &jwk_bytes).unwrap();
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,15 +11,21 @@ repository = "https://github.com/informationsea/jsonwebkey-rs"
|
|||
keywords = ["jsonwebkey", "jsonwebtoken"]
|
||||
categories = ["authentication"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
full = ["jsonwebtoken", "pem_support"]
|
||||
pem_support = ["simple_asn1", "pem"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
pem = "0.8"
|
||||
simple_asn1 = "0.4"
|
||||
num-bigint = "0.2"
|
||||
pem = { version = "0.8", optional = true }
|
||||
jsonwebtoken = { version = "^7.2", optional = true }
|
||||
simple_asn1 = { version = "^0.5.1", optional = true }
|
||||
num-bigint = "^0.3"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
base64-url = "1"
|
||||
base64 = "^0.13"
|
||||
lazy_static = "1"
|
||||
anyhow = "1"
|
||||
thiserror = "1"
|
||||
thiserror = "1"
|
||||
|
|
|
|||
170
jsonwebkey-convert/src/der.rs
Normal file
170
jsonwebkey-convert/src/der.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
use super::*;
|
||||
use lazy_static::lazy_static;
|
||||
use num_bigint::ToBigInt;
|
||||
use simple_asn1::{oid, ASN1Block, BigUint, OID};
|
||||
|
||||
lazy_static! {
|
||||
static ref RSA_OID: simple_asn1::OID = oid!(1, 2, 840, 113549, 1, 1, 1);
|
||||
}
|
||||
|
||||
pub trait ToPem {
|
||||
fn to_der(&self) -> Result<Vec<u8>, Error>;
|
||||
fn to_pem(&self) -> Result<String, Error> {
|
||||
let der = self.to_der()?;
|
||||
let pem = pem::Pem {
|
||||
tag: "PUBLIC KEY".to_string(),
|
||||
contents: der,
|
||||
};
|
||||
Ok(pem::encode(&pem))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPem for RSAPublicKey {
|
||||
fn to_der(&self) -> Result<Vec<u8>, Error> {
|
||||
let pubkey_asn1 = ASN1Block::Sequence(
|
||||
0,
|
||||
vec![
|
||||
ASN1Block::Integer(0, self.n.big_uint.to_bigint().unwrap()),
|
||||
ASN1Block::Integer(0, self.e.big_uint.to_bigint().unwrap()),
|
||||
],
|
||||
);
|
||||
let pubkey_der = simple_asn1::to_der(&pubkey_asn1)?;
|
||||
let asn1 = ASN1Block::Sequence(
|
||||
0,
|
||||
vec![
|
||||
ASN1Block::Sequence(
|
||||
0,
|
||||
vec![
|
||||
ASN1Block::ObjectIdentifier(0, RSA_OID.clone()),
|
||||
ASN1Block::Null(0),
|
||||
],
|
||||
),
|
||||
ASN1Block::BitString(0, pubkey_der.len() * 8, pubkey_der),
|
||||
],
|
||||
);
|
||||
|
||||
Ok(simple_asn1::to_der(&asn1)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromPem: Sized {
|
||||
fn from_pem<T: AsRef<[u8]>>(pem: T) -> Result<Self, Error> {
|
||||
let data = pem::parse(pem)?;
|
||||
Self::from_der(&data.contents)
|
||||
}
|
||||
fn from_der(der: &[u8]) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
impl FromPem for RSAPublicKey {
|
||||
fn from_der(der: &[u8]) -> Result<Self, Error> {
|
||||
let ans1_block_vec = simple_asn1::from_der(der)?;
|
||||
if ans1_block_vec.len() != 1 {
|
||||
return Err(Error::PubKeyParse(
|
||||
"Invalid number of sequence: length of 1ans1_block_vec is not 1",
|
||||
));
|
||||
}
|
||||
let ans1_seq = if let ASN1Block::Sequence(_, d) = &ans1_block_vec[0] {
|
||||
d
|
||||
} else {
|
||||
return Err(Error::PubKeyParse("Invalid format: 2"));
|
||||
};
|
||||
if ans1_seq.len() != 2 {
|
||||
return Err(Error::PubKeyParse("Invalid number of sequence: 3"));
|
||||
}
|
||||
//println!("pubkey der: {:?}", ans1_seq);
|
||||
|
||||
let oid_seq = if let ASN1Block::Sequence(_, s) = &ans1_seq[0] {
|
||||
s
|
||||
} else {
|
||||
return Err(Error::PubKeyParse("Invalid format: 3"));
|
||||
};
|
||||
let oid = if let ASN1Block::ObjectIdentifier(_, o) = &oid_seq[0] {
|
||||
o
|
||||
} else {
|
||||
return Err(Error::PubKeyParse("Invalid format: 4"));
|
||||
};
|
||||
if oid != *RSA_OID {
|
||||
return Err(Error::PubKeyParse("Invalid format: 5"));
|
||||
}
|
||||
|
||||
let bit_string = if let ASN1Block::BitString(_, _, s) = &ans1_seq[1] {
|
||||
s
|
||||
} else {
|
||||
return Err(Error::PubKeyParse("Invalid format: 6"));
|
||||
};
|
||||
|
||||
let parsed_der = simple_asn1::from_der(&bit_string).map_err(Error::ANS1DecodeError)?;
|
||||
if parsed_der.len() != 1 {
|
||||
return Err(Error::PubKeyParse("Invalid format: 7"));
|
||||
}
|
||||
let pubkey_seq = if let ASN1Block::Sequence(_, s) = &parsed_der[0] {
|
||||
s
|
||||
} else {
|
||||
return Err(Error::PubKeyParse("Invalid format: 8"));
|
||||
};
|
||||
if pubkey_seq.len() != 2 {
|
||||
return Err(Error::PubKeyParse("Invalid format: 9"));
|
||||
}
|
||||
let n = if let ASN1Block::Integer(_, n) = &pubkey_seq[0] {
|
||||
n
|
||||
} else {
|
||||
return Err(Error::PubKeyParse("Invalid format: 10"));
|
||||
};
|
||||
let e = if let ASN1Block::Integer(_, e) = &pubkey_seq[1] {
|
||||
e
|
||||
} else {
|
||||
return Err(Error::PubKeyParse("Invalid format: 11"));
|
||||
};
|
||||
|
||||
let rsa_pub_key = RSAPublicKey {
|
||||
generic: Generic {
|
||||
kty: KeyType::Rsa,
|
||||
use_: None,
|
||||
key_ops: None,
|
||||
alg: None,
|
||||
kid: None,
|
||||
x5u: None,
|
||||
x5c: None,
|
||||
x5t: None,
|
||||
x5t_s256: None,
|
||||
},
|
||||
e: e.to_biguint().unwrap().into(),
|
||||
n: n.to_biguint().unwrap().into(),
|
||||
};
|
||||
|
||||
Ok(rsa_pub_key)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_convert_to_pem1() {
|
||||
let jwk: RSAPublicKey =
|
||||
serde_json::from_str(include_str!("../testfiles/test1.json")).unwrap();
|
||||
let pem = jwk.to_pem().unwrap();
|
||||
let pem_expected = include_str!("../testfiles/test1.pem");
|
||||
assert_eq!(pem, pem_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_to_pem2() {
|
||||
let jwk: RSAPublicKey =
|
||||
serde_json::from_str(include_str!("../testfiles/test2.json")).unwrap();
|
||||
let pem = jwk.to_pem().unwrap();
|
||||
let pem_expected = include_str!("../testfiles/test2.pem");
|
||||
assert_eq!(pem, pem_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convert_from_pem1() {
|
||||
let pem = include_str!("../testfiles/test1.pem");
|
||||
let mut jwk = RSAPublicKey::from_pem(pem).unwrap();
|
||||
jwk.generic.kid = Some("fe41cf0f-7901-489f-9d4e-1437d6c1aa1f".to_string());
|
||||
let jwk_expected: RSAPublicKey =
|
||||
serde_json::from_str(include_str!("../testfiles/test1.json")).unwrap();
|
||||
assert_eq!(jwk, jwk_expected);
|
||||
}
|
||||
}
|
||||
12
jsonwebkey-convert/src/jwt.rs
Normal file
12
jsonwebkey-convert/src/jwt.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use super::*;
|
||||
use jsonwebtoken::DecodingKey;
|
||||
|
||||
pub trait ToDecodingKey {
|
||||
fn to_decoding_key(&'_ self) -> DecodingKey<'_>;
|
||||
}
|
||||
|
||||
impl ToDecodingKey for RSAPublicKey {
|
||||
fn to_decoding_key(&'_ self) -> DecodingKey<'_> {
|
||||
DecodingKey::from_rsa_components(&self.n.base64, &self.e.base64)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,351 +1,495 @@
|
|||
//! # jsonwebkey-convert
|
||||
//! Convert an RSA public key between Json Web Key and DER/PEM format.
|
||||
//! Handle Json Web Key without nightly rust compiler.
|
||||
//!
|
||||
//! ## Load JSON Web Key Set
|
||||
//!
|
||||
//! ```
|
||||
//! use jsonwebkey_convert::JsonWebKeySet;
|
||||
//! # use jsonwebkey_convert::Error;
|
||||
//!
|
||||
//! # fn main() -> Result<(), Error> {
|
||||
//! # let jwks_str = include_str!("../testfiles/example-public-key.json");
|
||||
//! let jwks: JsonWebKeySet = jwks_str.parse()?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Convert PEM to JWK
|
||||
//! `pem_support` feature is required.
|
||||
//!
|
||||
//! ```
|
||||
//! use jsonwebkey_convert::*;
|
||||
//! use jsonwebkey_convert::der::FromPem;
|
||||
//!
|
||||
//! # fn main() -> Result<(), JWKConvertError> {
|
||||
//! # let pem_data = include_bytes!("../testfiles/test1.pem");
|
||||
//! let pem_rsa = load_pem(&pem_data[..])?;
|
||||
//! let jwk_data = RSAJWK {
|
||||
//! kid: Some("3f5fbba0-06c4-467c-8d5e-e935a71437b0".to_string()),
|
||||
//! jwk_use: Some("sig".to_string()),
|
||||
//! pubkey: pem_rsa
|
||||
//! };
|
||||
//! let jwk_byte_vec = jwk_data.to_jwk()?;
|
||||
//! # fn main() -> Result<(), Error> {
|
||||
//! # let pem_data = include_str!("../testfiles/test1.pem");
|
||||
//! let rsa_jwk = RSAPublicKey::from_pem(pem_data)?;
|
||||
//! let jwk_byte_vec = serde_json::to_string(&rsa_jwk);
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Convert JWK to PEM
|
||||
//! `pem_support` feature is required.
|
||||
//!
|
||||
//! ```
|
||||
//! use jsonwebkey_convert::*;
|
||||
//! use jsonwebkey_convert::der::ToPem;
|
||||
//!
|
||||
//! # fn main() -> Result<(), JWKConvertError> {
|
||||
//! # let jwk_byte_vec = include_bytes!("../testfiles/test1.json");
|
||||
//! let jwk_data = load_jwk(&jwk_byte_vec[..])?;
|
||||
//! let rsa_pubkey = jwk_data.pubkey;
|
||||
//! let pem_string = rsa_pubkey.to_pem()?;
|
||||
//! # fn main() -> Result<(), Error> {
|
||||
//! # let jwk_data = include_str!("../testfiles/test1.json");
|
||||
//! let rsa_jwk: RSAPublicKey = jwk_data.parse()?;
|
||||
//! let pem_data = rsa_jwk.to_pem()?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use simple_asn1::{oid, ASN1Block, BigInt, BigUint, OID};
|
||||
use std::convert::TryInto;
|
||||
/// DER and PEM format support
|
||||
#[cfg(feature = "simple_asn1")]
|
||||
pub mod der;
|
||||
|
||||
/// Json Web Token support
|
||||
#[cfg(feature = "jsonwebtoken")]
|
||||
pub mod jwt;
|
||||
|
||||
use num_bigint::BigUint;
|
||||
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum JWKConvertError {
|
||||
#[error("JWK Parse Error: {0}")]
|
||||
JWKParseError(&'static str),
|
||||
pub enum Error {
|
||||
#[error("Public Key Parse Error: {0}")]
|
||||
PubKeyParse(&'static str),
|
||||
#[cfg(feature = "simple_asn1")]
|
||||
#[error(transparent)]
|
||||
ANS1DecodeError(#[from] simple_asn1::ASN1DecodeErr),
|
||||
#[cfg(feature = "simple_asn1")]
|
||||
#[error(transparent)]
|
||||
ANS1EncodeError(#[from] simple_asn1::ASN1EncodeErr),
|
||||
#[error(transparent)]
|
||||
#[cfg(feature = "pem")]
|
||||
PEMParseError(#[from] pem::PemError),
|
||||
#[error(transparent)]
|
||||
Base64UrlError(#[from] base64_url::base64::DecodeError),
|
||||
Base64UrlError(#[from] base64::DecodeError),
|
||||
#[error(transparent)]
|
||||
JSONParseError(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref RSA_OID: simple_asn1::OID = oid!(1, 2, 840, 113549, 1, 1, 1);
|
||||
/// A set of Json Web Keys
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct JsonWebKeySet {
|
||||
pub keys: Vec<JsonWebKey>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
struct RSAPubKeyJWK {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
kid: Option<String>,
|
||||
kty: String,
|
||||
|
||||
#[serde(rename = "use")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
use_: Option<String>,
|
||||
n: String,
|
||||
e: String,
|
||||
}
|
||||
|
||||
/// RSA Public Key with kid and use
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct RSAJWK {
|
||||
pub kid: Option<String>,
|
||||
pub jwk_use: Option<String>,
|
||||
pub pubkey: RSAPubKey,
|
||||
}
|
||||
|
||||
impl Into<RSAPubKey> for RSAJWK {
|
||||
fn into(self) -> RSAPubKey {
|
||||
self.pubkey
|
||||
impl JsonWebKeySet {
|
||||
pub fn from_bytes(data: &[u8]) -> Result<JsonWebKeySet, Error> {
|
||||
Ok(serde_json::from_slice(data)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// RSA Public Key
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct RSAPubKey {
|
||||
pub n: BigInt,
|
||||
pub e: BigInt,
|
||||
impl FromStr for JsonWebKeySet {
|
||||
type Err = Error;
|
||||
fn from_str(data: &str) -> Result<JsonWebKeySet, Error> {
|
||||
Ok(serde_json::from_str(data)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<RSAJWK> for RSAPubKey {
|
||||
fn into(self) -> RSAJWK {
|
||||
RSAJWK {
|
||||
kid: None,
|
||||
jwk_use: None,
|
||||
pubkey: self,
|
||||
/// A JSON Web Key
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum JsonWebKey {
|
||||
ECPrivateKey {
|
||||
#[serde(flatten)]
|
||||
value: ECPrivateKey,
|
||||
},
|
||||
ECPublicKey {
|
||||
#[serde(flatten)]
|
||||
value: ECPublicKey,
|
||||
},
|
||||
RSAPrivateKey {
|
||||
#[serde(flatten)]
|
||||
value: Box<RSAPrivateKey>,
|
||||
},
|
||||
RSAPublicKey {
|
||||
#[serde(flatten)]
|
||||
value: RSAPublicKey,
|
||||
},
|
||||
SymmetricKey {
|
||||
#[serde(flatten)]
|
||||
value: SymmetricKey,
|
||||
},
|
||||
}
|
||||
impl FromStr for JsonWebKey {
|
||||
type Err = Error;
|
||||
fn from_str(data: &str) -> Result<JsonWebKey, Error> {
|
||||
Ok(serde_json::from_str(data)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonWebKey {
|
||||
pub fn from_bytes(data: &[u8]) -> Result<JsonWebKey, Error> {
|
||||
Ok(serde_json::from_slice(data)?)
|
||||
}
|
||||
|
||||
pub fn ec_private_key(&self) -> Option<&ECPrivateKey> {
|
||||
match self {
|
||||
JsonWebKey::ECPrivateKey { value } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ec_public_key(&self) -> Option<&ECPublicKey> {
|
||||
match self {
|
||||
JsonWebKey::ECPublicKey { value } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rsa_private_key(&self) -> Option<&RSAPrivateKey> {
|
||||
match self {
|
||||
JsonWebKey::RSAPrivateKey { value } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rsa_public_key(&self) -> Option<&RSAPublicKey> {
|
||||
match self {
|
||||
JsonWebKey::RSAPublicKey { value } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symmetric_key(&self) -> Option<&SymmetricKey> {
|
||||
match self {
|
||||
JsonWebKey::SymmetricKey { value } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<RSAJWK> for RSAPubKeyJWK {
|
||||
type Error = JWKConvertError;
|
||||
fn try_into(self) -> Result<RSAJWK, Self::Error> {
|
||||
if self.kty != "RSA" {
|
||||
return Err(JWKConvertError::JWKParseError("Unspported type"));
|
||||
}
|
||||
let n = base64_url::decode(&self.n)?;
|
||||
let e = base64_url::decode(&self.e)?;
|
||||
Ok(RSAJWK {
|
||||
kid: self.kid,
|
||||
jwk_use: self.use_,
|
||||
pubkey: RSAPubKey {
|
||||
n: BigInt::from_bytes_be(num_bigint::Sign::Plus, &n),
|
||||
e: BigInt::from_bytes_be(num_bigint::Sign::Plus, &e),
|
||||
},
|
||||
#[derive(Debug, Clone, PartialEq, Copy, Serialize, Deserialize)]
|
||||
pub enum ECCurveParameter {
|
||||
#[serde(rename = "P-256")]
|
||||
P256,
|
||||
#[serde(rename = "P-384")]
|
||||
P384,
|
||||
#[serde(rename = "P-521")]
|
||||
P521,
|
||||
}
|
||||
|
||||
/// BASE64 encoded big integer
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Base64BigUint {
|
||||
big_uint: BigUint,
|
||||
base64: String,
|
||||
}
|
||||
|
||||
impl Base64BigUint {
|
||||
pub fn to_base64url(&self) -> String {
|
||||
base64::encode_config(&self.big_uint.to_bytes_be(), base64::URL_SAFE_NO_PAD)
|
||||
}
|
||||
|
||||
pub fn from_base64url(value: &str) -> Result<Self, Error> {
|
||||
Ok(Base64BigUint {
|
||||
big_uint: BigUint::from_bytes_be(&base64::decode_config(
|
||||
value,
|
||||
base64::URL_SAFE_NO_PAD,
|
||||
)?),
|
||||
base64: value.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RSAJWK {
|
||||
pub fn to_jwk(&self) -> Result<String, JWKConvertError> {
|
||||
let jwk = RSAPubKeyJWK {
|
||||
kid: self.kid.clone(),
|
||||
kty: "RSA".to_string(),
|
||||
use_: self.jwk_use.clone(),
|
||||
n: base64_url::encode(&self.pubkey.n.to_bytes_be().1),
|
||||
e: base64_url::encode(&self.pubkey.e.to_bytes_be().1),
|
||||
};
|
||||
serde_json::to_string(&jwk).map_err(JWKConvertError::JSONParseError)
|
||||
impl Into<BigUint> for Base64BigUint {
|
||||
fn into(self) -> BigUint {
|
||||
self.big_uint
|
||||
}
|
||||
}
|
||||
|
||||
impl RSAPubKey {
|
||||
pub fn to_der(&self) -> Result<Vec<u8>, JWKConvertError> {
|
||||
let pubkey_asn1 = ASN1Block::Sequence(
|
||||
0,
|
||||
vec![
|
||||
ASN1Block::Integer(0, self.n.clone()),
|
||||
ASN1Block::Integer(0, self.e.clone()),
|
||||
],
|
||||
);
|
||||
let pubkey_der = simple_asn1::to_der(&pubkey_asn1)?;
|
||||
let asn1 = ASN1Block::Sequence(
|
||||
0,
|
||||
vec![
|
||||
ASN1Block::Sequence(
|
||||
0,
|
||||
vec![
|
||||
ASN1Block::ObjectIdentifier(0, RSA_OID.clone()),
|
||||
ASN1Block::Null(0),
|
||||
],
|
||||
),
|
||||
ASN1Block::BitString(0, pubkey_der.len() * 8, pubkey_der),
|
||||
],
|
||||
);
|
||||
|
||||
Ok(simple_asn1::to_der(&asn1)?)
|
||||
}
|
||||
|
||||
pub fn to_pem(&self) -> Result<String, JWKConvertError> {
|
||||
let der = self.to_der()?;
|
||||
let pem = pem::Pem {
|
||||
tag: "PUBLIC KEY".to_string(),
|
||||
contents: der,
|
||||
};
|
||||
Ok(pem::encode(&pem))
|
||||
impl Into<Base64BigUint> for BigUint {
|
||||
fn into(self) -> Base64BigUint {
|
||||
Base64BigUint {
|
||||
base64: base64::encode_config(self.to_bytes_be(), base64::URL_SAFE_NO_PAD),
|
||||
big_uint: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a Json Web Key from bytes slice
|
||||
pub fn load_jwk(data: &[u8]) -> Result<RSAJWK, JWKConvertError> {
|
||||
let jwk: RSAPubKeyJWK = serde_json::from_slice(data)?;
|
||||
Ok(jwk.try_into()?)
|
||||
impl Serialize for Base64BigUint {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.to_base64url().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Load an RSA public key from DER format
|
||||
pub fn load_der(data: &[u8]) -> Result<RSAPubKey, JWKConvertError> {
|
||||
let ans1_block_vec = simple_asn1::from_der(data)?;
|
||||
if ans1_block_vec.len() != 1 {
|
||||
return Err(JWKConvertError::PubKeyParse(
|
||||
"Invalid number of sequence: 1",
|
||||
));
|
||||
impl<'de> Deserialize<'de> for Base64BigUint {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let value = String::deserialize(deserializer)?;
|
||||
Ok(Base64BigUint::from_base64url(&value).map_err(|e| {
|
||||
D::Error::custom(format!("failed to decode BASE64 URL: {} : {}", value, e))
|
||||
})?)
|
||||
}
|
||||
let ans1_seq = if let ASN1Block::Sequence(_, d) = &ans1_block_vec[0] {
|
||||
d
|
||||
} else {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 2"));
|
||||
};
|
||||
if ans1_seq.len() != 2 {
|
||||
return Err(JWKConvertError::PubKeyParse(
|
||||
"Invalid number of sequence: 3",
|
||||
));
|
||||
}
|
||||
//println!("pubkey der: {:?}", ans1_seq);
|
||||
|
||||
let oid_seq = if let ASN1Block::Sequence(_, s) = &ans1_seq[0] {
|
||||
s
|
||||
} else {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 3"));
|
||||
};
|
||||
let oid = if let ASN1Block::ObjectIdentifier(_, o) = &oid_seq[0] {
|
||||
o
|
||||
} else {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 4"));
|
||||
};
|
||||
if oid != *RSA_OID {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 5"));
|
||||
}
|
||||
|
||||
let bit_string = if let ASN1Block::BitString(_, _, s) = &ans1_seq[1] {
|
||||
s
|
||||
} else {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 6"));
|
||||
};
|
||||
|
||||
let parsed_der =
|
||||
simple_asn1::from_der(&bit_string).map_err(JWKConvertError::ANS1DecodeError)?;
|
||||
if parsed_der.len() != 1 {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 7"));
|
||||
}
|
||||
let pubkey_seq = if let ASN1Block::Sequence(_, s) = &parsed_der[0] {
|
||||
s
|
||||
} else {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 8"));
|
||||
};
|
||||
if pubkey_seq.len() != 2 {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 9"));
|
||||
}
|
||||
let n = if let ASN1Block::Integer(_, n) = &pubkey_seq[0] {
|
||||
n
|
||||
} else {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 10"));
|
||||
};
|
||||
let e = if let ASN1Block::Integer(_, e) = &pubkey_seq[1] {
|
||||
e
|
||||
} else {
|
||||
return Err(JWKConvertError::PubKeyParse("Invalid format: 11"));
|
||||
};
|
||||
|
||||
let rsa_pub_key = RSAPubKey {
|
||||
e: e.clone(),
|
||||
n: n.clone(),
|
||||
};
|
||||
|
||||
Ok(rsa_pub_key)
|
||||
}
|
||||
|
||||
/// Load an RSA public key from PEM format
|
||||
pub fn load_pem(data: &[u8]) -> Result<RSAPubKey, JWKConvertError> {
|
||||
let data = pem::parse(data)?;
|
||||
load_der(&data.contents)
|
||||
// RFC 7518
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
//#[serde(deny_unknown_fields)]
|
||||
pub struct ECPublicKey {
|
||||
#[serde(flatten)]
|
||||
pub generic: Generic,
|
||||
pub crv: ECCurveParameter,
|
||||
pub x: Base64BigUint,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub y: Option<Base64BigUint>,
|
||||
}
|
||||
|
||||
impl FromStr for ECPublicKey {
|
||||
type Err = Error;
|
||||
fn from_str(data: &str) -> Result<Self, Error> {
|
||||
Ok(serde_json::from_str(data)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
//#[serde(deny_unknown_fields)]
|
||||
pub struct ECPrivateKey {
|
||||
#[serde(flatten)]
|
||||
pub public_key: ECPublicKey,
|
||||
pub d: Base64BigUint,
|
||||
}
|
||||
|
||||
impl FromStr for ECPrivateKey {
|
||||
type Err = Error;
|
||||
fn from_str(data: &str) -> Result<Self, Error> {
|
||||
Ok(serde_json::from_str(data)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
//#[serde(deny_unknown_fields)]
|
||||
pub struct RSAPublicKey {
|
||||
#[serde(flatten)]
|
||||
pub generic: Generic,
|
||||
pub n: Base64BigUint,
|
||||
pub e: Base64BigUint,
|
||||
}
|
||||
|
||||
impl FromStr for RSAPublicKey {
|
||||
type Err = Error;
|
||||
fn from_str(data: &str) -> Result<Self, Error> {
|
||||
Ok(serde_json::from_str(data)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
//#[serde(deny_unknown_fields)]
|
||||
pub struct RSAPrivateKey {
|
||||
#[serde(flatten)]
|
||||
pub public_key: RSAPublicKey,
|
||||
pub d: Base64BigUint,
|
||||
#[serde(flatten)]
|
||||
pub optimizations: Option<RSAPrivateKeyOptimizations>,
|
||||
pub oth: Option<Vec<RSAPrivateKeyOtherPrimesInfo>>,
|
||||
}
|
||||
|
||||
impl FromStr for RSAPrivateKey {
|
||||
type Err = Error;
|
||||
fn from_str(data: &str) -> Result<Self, Error> {
|
||||
Ok(serde_json::from_str(data)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
//#[serde(deny_unknown_fields)]
|
||||
pub struct RSAPrivateKeyOptimizations {
|
||||
pub p: Base64BigUint,
|
||||
pub q: Base64BigUint,
|
||||
pub dp: Base64BigUint,
|
||||
pub dq: Base64BigUint,
|
||||
pub qi: Base64BigUint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
//#[serde(deny_unknown_fields)]
|
||||
pub struct RSAPrivateKeyOtherPrimesInfo {
|
||||
pub r: Base64BigUint,
|
||||
pub d: Base64BigUint,
|
||||
pub t: Base64BigUint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SymmetricKey {
|
||||
#[serde(flatten)]
|
||||
pub generic: Generic,
|
||||
pub k: String,
|
||||
}
|
||||
|
||||
impl FromStr for SymmetricKey {
|
||||
type Err = Error;
|
||||
fn from_str(data: &str) -> Result<Self, Error> {
|
||||
Ok(serde_json::from_str(data)?)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type of JWK.
|
||||
/// See RFC 7518 Section 6.1.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum KeyType {
|
||||
#[serde(rename = "EC")]
|
||||
EllipticCurve,
|
||||
#[serde(rename = "RSA")]
|
||||
Rsa,
|
||||
#[serde(rename = "oct")]
|
||||
OctetSequence,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub enum KeyUse {
|
||||
#[serde(rename = "sig")]
|
||||
Signature,
|
||||
#[serde(rename = "enc")]
|
||||
Encryption,
|
||||
}
|
||||
|
||||
/// Generic parameters for JSON Web Key.
|
||||
/// See RFC 7517, Section 4.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Generic {
|
||||
// Generic parameters
|
||||
pub kty: KeyType,
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "use")]
|
||||
pub use_: Option<KeyUse>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub key_ops: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub alg: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub kid: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub x5u: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub x5c: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub x5t: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none", rename = "x5t#S256")]
|
||||
pub x5t_s256: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod test {
|
||||
use super::*;
|
||||
use std::fs::File;
|
||||
use std::str;
|
||||
|
||||
#[test]
|
||||
fn load_jwk1() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut reader = File::open("testfiles/test1.json")?;
|
||||
let data: RSAPubKeyJWK = serde_json::from_reader(&mut reader)?;
|
||||
fn load_rsa_pubkey1() -> Result<(), serde_json::Error> {
|
||||
let pubkey_json: JsonWebKey =
|
||||
serde_json::from_str(include_str!("../testfiles/test1.json"))?;
|
||||
let pubkey_data = pubkey_json.rsa_public_key().unwrap();
|
||||
assert_eq!(pubkey_data.generic.kty, KeyType::Rsa);
|
||||
assert_eq!(pubkey_data.e.big_uint, BigUint::from(65537u64));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_rsa_pubkey2() -> Result<(), serde_json::Error> {
|
||||
let pubkey_json: JsonWebKey =
|
||||
serde_json::from_str(include_str!("../testfiles/test2.json"))?;
|
||||
let pubkey_data = pubkey_json.rsa_public_key().unwrap();
|
||||
assert_eq!(pubkey_data.generic.kty, KeyType::Rsa);
|
||||
assert_eq!(pubkey_data.generic.alg.as_ref().unwrap(), "RS256");
|
||||
assert_eq!(pubkey_data.generic.use_.unwrap(), KeyUse::Signature);
|
||||
assert_eq!(
|
||||
data,
|
||||
RSAPubKeyJWK {
|
||||
kid: Some("fe41cf0f-7901-489f-9d4e-1437d6c1aa1f".to_string()),
|
||||
kty: "RSA".to_string(),
|
||||
use_: None,
|
||||
n: "rAINWn65QjweP2o9mzZF0dj1V_qlyBs9anRd_OA_iJUk2vTXU9FrzPl1AT8xT570ZGq7UW_dLE-ANUcL10Xr2I-bzVL9IL6aYnjO9L_lqbilfScfJSfT81Oho-vnj5FJH1LaD6s90vStEcSH49kwNoDDK9BXYovFEtxFeFx-H0eRoxHdxo7_91YBPyez4JjBYrBs29Sro2DbVSRxaW384HWXhEYNtGp2Z3Qf22t2o4tUkfxs_fuaU24mwKCWykfnQ5Cq8V7NAIqgWxhVsubjy9yCZ0kFxCNf_cs9hkWIYtVNSDjFg9P30bwy1-37Y01Lb4KVBW6fN7whCq_y-NlJWQ".to_string(),
|
||||
e: "AQAB".to_string()
|
||||
}
|
||||
pubkey_data.generic.kid.as_ref().unwrap(),
|
||||
"ctFNPw6mrKynlD3atDovZGBlbWRXj7IK0IBODJ_hqeI"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_jwk2() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut reader = File::open("testfiles/test2.json")?;
|
||||
let data: RSAPubKeyJWK = serde_json::from_reader(&mut reader)?;
|
||||
|
||||
assert_eq!(
|
||||
data,
|
||||
RSAPubKeyJWK {
|
||||
kid: Some("ctFNPw6mrKynlD3atDovZGBlbWRXj7IK0IBODJ_hqeI".to_string()),
|
||||
kty: "RSA".to_string(),
|
||||
use_: Some("sig".to_string()),
|
||||
n: "r3tms5oOWdyOO-XqMdNkLdp7tm5Eb7kY2ENPCCt-bpU6pC1-QOO3dfTs9LeiyeyonZpqD93ghW1pe5LB49rt1e2BqPNZdndGJZWmtAlv9YXCkLKat6GaG2e7gNzuq7Ls-my-vAYmS6B71KpkBTze2S3KcTjTEP6tPbJzgqZ6vPNK3EYbdCPZHi-QujRmGWUBeUdwsOnGWslaVlmkd4nIeqWYjV-mFD07WwB1y-pWBlC39A_RY4XUGP8WFxd0RSFNy3EoJw1yDK6_-1_xZZfzlRn0JpZsl6p-8zI8FgvMpQmXTSiAgfhYJGhBRZuvOPUrHBhwNE0GeqYYbUiOsXQHiw".to_string(),
|
||||
e: "AQAB".to_string()
|
||||
}
|
||||
pubkey_data.generic.x5t.as_ref().unwrap(),
|
||||
"ZsHe1ebgPQqmqNF8rjKqWEjh4hk"
|
||||
);
|
||||
assert_eq!(
|
||||
pubkey_data.generic.x5t_s256.as_ref().unwrap(),
|
||||
"VaYCCwkyvl8K71fldYXJtNjHAPTGom2ylqdAbedtKUI"
|
||||
);
|
||||
assert_eq!(pubkey_data.e.big_uint, BigUint::from(65537u64));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_der1() -> Result<(), JWKConvertError> {
|
||||
let pem_data = include_bytes!("../testfiles/test1.pem");
|
||||
let pem_rsa = load_pem(&pem_data[..])?;
|
||||
let jwk_data = include_bytes!("../testfiles/test1.json");
|
||||
let jwk_rsa = load_jwk(&jwk_data[..])?;
|
||||
assert_eq!(pem_rsa, jwk_rsa.pubkey);
|
||||
fn load_private_keys() -> Result<(), serde_json::Error> {
|
||||
let privkey_json: JsonWebKeySet =
|
||||
serde_json::from_str(include_str!("../testfiles/example-private-key.json"))?;
|
||||
assert_eq!(privkey_json.keys.len(), 2);
|
||||
|
||||
let generated_pem = jwk_rsa.pubkey.to_pem()?;
|
||||
assert_eq!(generated_pem, str::from_utf8(&pem_data[..]).unwrap());
|
||||
let ec_private_key = privkey_json.keys[0].ec_private_key().unwrap();
|
||||
assert_eq!(
|
||||
ec_private_key.public_key.generic.kty,
|
||||
KeyType::EllipticCurve
|
||||
);
|
||||
assert_eq!(ec_private_key.public_key.crv, ECCurveParameter::P256);
|
||||
assert_eq!(
|
||||
ec_private_key.public_key.generic.use_.unwrap(),
|
||||
KeyUse::Encryption
|
||||
);
|
||||
assert_eq!(ec_private_key.public_key.generic.kid.as_ref().unwrap(), "1");
|
||||
|
||||
let rsa_private_key = privkey_json.keys[1].rsa_private_key().unwrap();
|
||||
assert_eq!(rsa_private_key.public_key.generic.kty, KeyType::Rsa);
|
||||
assert!(rsa_private_key.optimizations.is_some());
|
||||
|
||||
let jwk_parsed: RSAPubKeyJWK =
|
||||
serde_json::from_slice(jwk_data).map_err(JWKConvertError::JSONParseError)?;
|
||||
let pem_jwk: RSAPubKeyJWK = serde_json::from_str(
|
||||
&RSAJWK {
|
||||
pubkey: pem_rsa,
|
||||
kid: jwk_rsa.kid.clone(),
|
||||
jwk_use: jwk_rsa.jwk_use,
|
||||
}
|
||||
.to_jwk()?,
|
||||
)
|
||||
.map_err(JWKConvertError::JSONParseError)?;
|
||||
assert_eq!(jwk_parsed, pem_jwk);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_der2() -> Result<(), JWKConvertError> {
|
||||
let pem_data = include_bytes!("../testfiles/test2.pem");
|
||||
let pem_rsa = load_pem(&pem_data[..])?;
|
||||
let jwk_data = include_bytes!("../testfiles/test2.json");
|
||||
let jwk_rsa = load_jwk(&jwk_data[..])?;
|
||||
assert_eq!(pem_rsa, jwk_rsa.pubkey);
|
||||
fn load_public_keys() -> Result<(), serde_json::Error> {
|
||||
let pubkey_json: JsonWebKeySet =
|
||||
serde_json::from_str(include_str!("../testfiles/example-public-key.json"))?;
|
||||
|
||||
let generated_pem = jwk_rsa.pubkey.to_pem()?;
|
||||
assert_eq!(generated_pem, str::from_utf8(&pem_data[..]).unwrap());
|
||||
let ec_public_key = pubkey_json.keys[0].ec_public_key().unwrap();
|
||||
assert_eq!(ec_public_key.generic.kty, KeyType::EllipticCurve);
|
||||
assert_eq!(ec_public_key.crv, ECCurveParameter::P256);
|
||||
assert_eq!(ec_public_key.generic.use_.unwrap(), KeyUse::Encryption);
|
||||
assert_eq!(ec_public_key.generic.kid.as_ref().unwrap(), "1");
|
||||
assert_eq!(
|
||||
ec_public_key.x.to_base64url(),
|
||||
"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4"
|
||||
);
|
||||
assert_eq!(
|
||||
ec_public_key.y.as_ref().unwrap().to_base64url(),
|
||||
"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM"
|
||||
);
|
||||
|
||||
let jwk_parsed: RSAPubKeyJWK = serde_json::from_slice(jwk_data)?;
|
||||
let pem_jwk: RSAPubKeyJWK = serde_json::from_str(
|
||||
&RSAJWK {
|
||||
pubkey: pem_rsa,
|
||||
kid: jwk_rsa.kid.clone(),
|
||||
jwk_use: jwk_rsa.jwk_use,
|
||||
}
|
||||
.to_jwk()?,
|
||||
)?;
|
||||
assert_eq!(jwk_parsed, pem_jwk);
|
||||
let rsa_public_key = pubkey_json.keys[1].rsa_public_key().unwrap();
|
||||
assert_eq!(rsa_public_key.generic.kty, KeyType::Rsa);
|
||||
assert_eq!(rsa_public_key.e.to_base64url(), "AQAB");
|
||||
assert_eq!(rsa_public_key.n.to_base64url(), "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw");
|
||||
assert_eq!(rsa_public_key.generic.alg.as_ref().unwrap(), "RS256");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_symmetric_keys() -> Result<(), serde_json::Error> {
|
||||
let symmetric_json: JsonWebKeySet =
|
||||
serde_json::from_str(include_str!("../testfiles/example-symmetric-keys.json"))?;
|
||||
|
||||
let symmetric_key = symmetric_json.keys[0].symmetric_key().unwrap();
|
||||
assert_eq!(symmetric_key.generic.kty, KeyType::OctetSequence);
|
||||
assert_eq!(symmetric_key.generic.alg.as_ref().unwrap(), "A128KW");
|
||||
assert_eq!(symmetric_key.k, "GawgguFyGrWKav7AX4VKUg");
|
||||
|
||||
let symmetric_key = symmetric_json.keys[1].symmetric_key().unwrap();
|
||||
assert_eq!(symmetric_key.generic.kty, KeyType::OctetSequence);
|
||||
assert_eq!(symmetric_key.k, "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow");
|
||||
assert_eq!(
|
||||
symmetric_key.generic.kid.as_ref().unwrap(),
|
||||
"HMAC key used in JWS spec Appendix A.1 example"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
jsonwebkey-convert/testfiles/example-private-key.json
Normal file
26
jsonwebkey-convert/testfiles/example-private-key.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
|
||||
"y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
|
||||
"d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
|
||||
"use": "enc",
|
||||
"kid": "1"
|
||||
},
|
||||
{
|
||||
"kty": "RSA",
|
||||
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
|
||||
"e": "AQAB",
|
||||
"d": "X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q",
|
||||
"p": "83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs",
|
||||
"q": "3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk",
|
||||
"dp": "G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0",
|
||||
"dq": "s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk",
|
||||
"qi": "GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU",
|
||||
"alg": "RS256",
|
||||
"kid": "2011-04-29"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
jsonwebkey-convert/testfiles/example-public-key.json
Normal file
19
jsonwebkey-convert/testfiles/example-public-key.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "EC",
|
||||
"crv": "P-256",
|
||||
"x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
|
||||
"y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
|
||||
"use": "enc",
|
||||
"kid": "1"
|
||||
},
|
||||
{
|
||||
"kty": "RSA",
|
||||
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
|
||||
"e": "AQAB",
|
||||
"alg": "RS256",
|
||||
"kid": "2011-04-29"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
jsonwebkey-convert/testfiles/example-symmetric-keys.json
Normal file
14
jsonwebkey-convert/testfiles/example-symmetric-keys.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"keys": [
|
||||
{
|
||||
"kty": "oct",
|
||||
"alg": "A128KW",
|
||||
"k": "GawgguFyGrWKav7AX4VKUg"
|
||||
},
|
||||
{
|
||||
"kty": "oct",
|
||||
"k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow",
|
||||
"kid": "HMAC key used in JWS spec Appendix A.1 example"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue