jose 0.3.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +7 -0
- data/{LICENSE.txt → LICENSE.md} +0 -0
- data/README.md +10 -3
- data/docs/EncryptionAlgorithms.md +55 -0
- data/docs/GettingStarted.md +137 -0
- data/docs/KeyGeneration.md +263 -0
- data/docs/SignatureAlgorithms.md +35 -0
- data/lib/jose.rb +86 -22
- data/lib/jose/jwa.rb +293 -0
- data/lib/jose/jwa/concat_kdf.rb +2 -0
- data/lib/jose/jwa/curve25519_ruby.rb +1 -1
- data/lib/jose/jwa/curve448_ruby.rb +1 -1
- data/lib/jose/jwa/ed448.rb +8 -8
- data/lib/jose/jwe.rb +819 -11
- data/lib/jose/jwe/alg_aes_gcm_kw.rb +1 -1
- data/lib/jose/jwe/alg_aes_kw.rb +1 -1
- data/lib/jose/jwe/alg_dir.rb +4 -4
- data/lib/jose/jwe/alg_ecdh_es.rb +45 -18
- data/lib/jose/jwe/alg_pbes2.rb +3 -3
- data/lib/jose/jwe/alg_rsa.rb +1 -1
- data/lib/jose/jwk.rb +639 -48
- data/lib/jose/jwk/kty_ec.rb +22 -4
- data/lib/jose/jwk/kty_oct.rb +16 -0
- data/lib/jose/jwk/kty_okp_ed25519.rb +36 -0
- data/lib/jose/jwk/kty_okp_ed25519ph.rb +36 -0
- data/lib/jose/jwk/kty_okp_ed448.rb +36 -0
- data/lib/jose/jwk/kty_okp_ed448ph.rb +36 -0
- data/lib/jose/jwk/kty_okp_x25519.rb +28 -0
- data/lib/jose/jwk/kty_okp_x448.rb +28 -0
- data/lib/jose/jwk/kty_rsa.rb +8 -0
- data/lib/jose/jwk/openssh_key.rb +278 -0
- data/lib/jose/jws.rb +486 -21
- data/lib/jose/jws/alg_none.rb +2 -2
- data/lib/jose/jwt.rb +208 -14
- data/lib/jose/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8804406a3e47d37b89da4315eb021d33d447ecf7
|
4
|
+
data.tar.gz: 9409c19e851ef2036b1fc843eeeaa3667c64ed40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ade14cfd65ca8cab5f24b5329448db0e0ee19d04fa71ec43fda67300197563c361c20f2229dba79f0b6b4b01cf9440482cae460324ce069fa782c66e3430b284
|
7
|
+
data.tar.gz: 391d322825bf7905f41800ae0ed77f7d29b0c27018ac06e65bcf3d04288fe579191a4278beac44e5a2f09e2e9350c22da4cab0189c126fef258888d4e1f53516
|
data/.travis.yml
CHANGED
data/.yardopts
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.0.0 (2016-05-07)
|
4
|
+
|
5
|
+
* Enhancements
|
6
|
+
* [Documentation!](http://www.rubydoc.info/gems/jose) Many thanks to [@soumyaray](https://github.com/soumyaray) for the motivation to improve documentation.
|
7
|
+
* Support for OpenSSH octet key pairs (for Ed25519).
|
8
|
+
* Better key management behavior associated with ECDH-ES algorithms.
|
9
|
+
|
3
10
|
## 0.3.1 (2016-05-05)
|
4
11
|
|
5
12
|
* Fixes
|
data/{LICENSE.txt → LICENSE.md}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# JOSE
|
2
2
|
|
3
|
-
[![
|
3
|
+
[![Travis](https://img.shields.io/travis/potatosalad/ruby-jose.svg?maxAge=2592000)](https://travis-ci.org/potatosalad/ruby-jose) [![Gem](https://img.shields.io/gem/v/jose.svg?maxAge=2592000)](https://rubygems.org/gems/jose) [![Docs](https://img.shields.io/badge/yard-docs-blue.svg?maxAge=2592000)](http://www.rubydoc.info/gems/jose) [![Inline docs](http://inch-ci.org/github/potatosalad/ruby-jose.svg?branch=master&style=shields)](http://inch-ci.org/github/potatosalad/ruby-jose)
|
4
4
|
|
5
5
|
JSON Object Signing and Encryption (JOSE) for Ruby.
|
6
6
|
|
@@ -24,7 +24,7 @@ Or install it yourself as:
|
|
24
24
|
|
25
25
|
## Usage
|
26
26
|
|
27
|
-
Better documentation is in progress, but
|
27
|
+
Better documentation is in progress, but here are a few resources to get started. First, a simple example of key generation and message signing:
|
28
28
|
|
29
29
|
```ruby
|
30
30
|
# Let's use our secret key "symmetric key" for use with
|
@@ -44,6 +44,14 @@ verified, message, = jwk.verify(signed)
|
|
44
44
|
# => [true, "test"]
|
45
45
|
```
|
46
46
|
|
47
|
+
More details and examples:
|
48
|
+
- [Getting Started](http://www.rubydoc.info/gems/jose/file/docs/GettingStarted.md)
|
49
|
+
- [Key Generation](http://www.rubydoc.info/gems/jose/file/docs/KeyGeneration.md)
|
50
|
+
- [Encryption Algorithms](http://www.rubydoc.info/gems/jose/file/docs/EncryptionAlgorithms.md)
|
51
|
+
- [Signature Algorithms](http://www.rubydoc.info/gems/jose/file/docs/SignatureAlgorithms.md)
|
52
|
+
|
53
|
+
Finally, the [erlang-jose documentation](https://hexdocs.pm/jose/) can provide more ideas of the functionality available in this gem.
|
54
|
+
|
47
55
|
## Development
|
48
56
|
|
49
57
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -57,4 +65,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/potato
|
|
57
65
|
## License
|
58
66
|
|
59
67
|
The gem is available as open source under the terms of the [MPL-2.0 License](http://opensource.org/licenses/MPL-2.0).
|
60
|
-
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# @title Encryption Algorithms
|
2
|
+
|
3
|
+
# Encryption Algorithms
|
4
|
+
|
5
|
+
The basic parameters for a {JOSE::JWE JOSE::JWE} header are:
|
6
|
+
|
7
|
+
- `"alg"` **(required)** - Key Management Algorithm used to encrypt or determine the value of the Content Encryption Key.
|
8
|
+
- `"enc"` **(required)** - Encryption Algorithm used to perform authenticated encryption on the plain text using the Content Encryption Key.
|
9
|
+
- `"zip"` *(optional)* - Compression Algorithm applied to the plaintext before encryption, if any.
|
10
|
+
|
11
|
+
See [RFC 7516](https://tools.ietf.org/html/rfc7516#section-4.1) for more information about other header parameters.
|
12
|
+
|
13
|
+
### `alg` Header Parameter
|
14
|
+
|
15
|
+
Here are the supported options for the `alg` parameter, grouped by similar funcionality:
|
16
|
+
|
17
|
+
- Single Asymmetric Public/Private Key Pair
|
18
|
+
- [`RSA1_5`](http://www.rubydoc.info/gems/jose/JOSE/JWE#RSA-group)
|
19
|
+
- [`RSA-OAEP`](http://www.rubydoc.info/gems/jose/JOSE/JWE#RSA-group)
|
20
|
+
- [`RSA-OAEP-256`](http://www.rubydoc.info/gems/jose/JOSE/JWE#RSA-group)
|
21
|
+
- Two Asymmetric Public/Private Key Pairs with Key Agreement
|
22
|
+
- [`ECDH-ES`](http://www.rubydoc.info/gems/jose/JOSE/JWE#ECDH-ES-group)
|
23
|
+
- [`ECDH-ES+A128KW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#ECDH-ES-group)
|
24
|
+
- [`ECDH-ES+A192KW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#ECDH-ES-group)
|
25
|
+
- [`ECDH-ES+A256KW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#ECDH-ES-group)
|
26
|
+
- Symmetric Password Based Key Derivation
|
27
|
+
- [`PBES2-HS256+A128KW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#PBES2-group)
|
28
|
+
- [`PBES2-HS384+A192KW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#PBES2-group)
|
29
|
+
- [`PBES2-HS512+A256KW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#PBES2-group)
|
30
|
+
- Symmetric Key Wrap
|
31
|
+
- [`A128GCMKW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESGCMKW-group)
|
32
|
+
- [`A192GCMKW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESGCMKW-group)
|
33
|
+
- [`A256GCMKW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESGCMKW-group)
|
34
|
+
- [`A128KW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESKW-group)
|
35
|
+
- [`A192KW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESKW-group)
|
36
|
+
- [`A256KW`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESKW-group)
|
37
|
+
- Symmetric Direct Key (known to both sides)
|
38
|
+
- [`dir`](http://www.rubydoc.info/gems/jose/JOSE/JWE#direct-group)
|
39
|
+
|
40
|
+
### `enc` Header Parameter
|
41
|
+
|
42
|
+
Here are the options for the `enc` parameter:
|
43
|
+
|
44
|
+
- [`A128CBC-HS256`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESCBC-group)
|
45
|
+
- [`A192CBC-HS384`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESCBC-group)
|
46
|
+
- [`A256CBC-HS512`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESCBC-group)
|
47
|
+
- [`A128GCM`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESGCM-group)
|
48
|
+
- [`A192GCM`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESGCM-group)
|
49
|
+
- [`A256GCM`](http://www.rubydoc.info/gems/jose/JOSE/JWE#AESGCM-group)
|
50
|
+
|
51
|
+
### `zip` Header Parameter
|
52
|
+
|
53
|
+
Here are the options for the `zip` parameter:
|
54
|
+
|
55
|
+
- [`DEF`](http://www.rubydoc.info/gems/jose/JOSE/JWE#DEF-group)
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# @title Getting Started
|
2
|
+
|
3
|
+
# Getting Started
|
4
|
+
|
5
|
+
JOSE stands for JSON Object Signing and Encryption which is a is a set of
|
6
|
+
standards established by the [JOSE Working Group](https://datatracker.ietf.org/wg/jose).
|
7
|
+
|
8
|
+
JOSE is split into 5 main components:
|
9
|
+
|
10
|
+
* {JOSE::JWA JOSE::JWA} - JSON Web Algorithms (JWA) {https://tools.ietf.org/html/rfc7518 RFC 7518}
|
11
|
+
* {JOSE::JWE JOSE::JWE} - JSON Web Encryption (JWE) {https://tools.ietf.org/html/rfc7516 RFC 7516}
|
12
|
+
* {JOSE::JWK JOSE::JWK} - JSON Web Key (JWK) {https://tools.ietf.org/html/rfc7517 RFC 7517}
|
13
|
+
* {JOSE::JWS JOSE::JWS} - JSON Web Signature (JWS) {https://tools.ietf.org/html/rfc7515 RFC 7515}
|
14
|
+
* {JOSE::JWT JOSE::JWT} - JSON Web Token (JWT) {https://tools.ietf.org/html/rfc7519 RFC 7519}
|
15
|
+
|
16
|
+
Additional specifications and drafts implemented:
|
17
|
+
|
18
|
+
* JSON Web Key (JWK) Thumbprint [RFC 7638](https://tools.ietf.org/html/rfc7638)
|
19
|
+
* JWS Unencoded Payload Option [draft-ietf-jose-jws-signing-input-options-04](https://tools.ietf.org/html/draft-ietf-jose-jws-signing-input-options-04)
|
20
|
+
|
21
|
+
## More Information
|
22
|
+
|
23
|
+
* {file:docs/KeyGeneration.md}
|
24
|
+
* {file:docs/EncryptionAlgorithms.md}
|
25
|
+
* {file:docs/SignatureAlgorithms.md}
|
26
|
+
|
27
|
+
## Usage Examples
|
28
|
+
|
29
|
+
The simplest combination might be `{"alg":"dir","enc":"A128GCM"}` which requires a 128-bit (or 16-byte) key that must be fully known by both parties.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# Alice wants to send Bob an encrypted message.
|
33
|
+
# Both Alice and Bob know about the 128-bit key "this is 16 bytes".
|
34
|
+
|
35
|
+
# Alice encrypts the plain_text and sends cipher_text to Bob.
|
36
|
+
plain_text = "Hello, World!"
|
37
|
+
jwk = JOSE::JWK.from_oct("this is 16 bytes")
|
38
|
+
cipher_text = jwk.block_encrypt(plain_text, {"alg" => "dir", "enc" => "A128GCM"}).compact
|
39
|
+
# => "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..oSOfVgfW5dPo4Mwx.x3N6TNI0pTYlBVHiSA.Uu_kROoBRWL0Hb0BH150Zg"
|
40
|
+
|
41
|
+
# Bob decrypts the cipher_text using the shared secret key.
|
42
|
+
cipher_text = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..oSOfVgfW5dPo4Mwx.x3N6TNI0pTYlBVHiSA.Uu_kROoBRWL0Hb0BH150Zg"
|
43
|
+
jwk = JOSE::JWK.from_oct("this is 16 bytes")
|
44
|
+
plain_text, = jwk.block_decrypt(cipher_text)
|
45
|
+
# => "Hello, World!"
|
46
|
+
```
|
47
|
+
|
48
|
+
A more complex combination might be `{"alg":"PBES2-HS256+A128KW","enc":"A128GCM","p2c":4096}` which uses a passphrase of any length and generates the Content Encryption Key of the correct length based on that passphrase. The `p2c` in the JSON specifies that 4096 rounds will be done which slows down any brute force attacks trying to determine the passphrase.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# Alice wants to send Bob an encrypted message.
|
52
|
+
# Both Alice and Bob know about the passphrase "alice and bob's secret".
|
53
|
+
|
54
|
+
# Alice encrypts the plain_text and sends cipher_text to Bob.
|
55
|
+
plain_text = "Hello, World!"
|
56
|
+
jwk = JOSE::JWK.from_oct("alice and bob's secret")
|
57
|
+
cipher_text = jwk.block_encrypt(plain_text, {"alg" => "PBES2-HS256+A128KW", "enc" => "A128GCM", "p2c" => 4096}).compact
|
58
|
+
# => "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjo0MDk2LCJwMnMiOiJfZEllWDFGX29zUSJ9.B8vGlGGJBNSeGiaOKFJq3MXqhvK5fihL.WUt7m0TldTqW4eNb.wyjPCvfglCovqEd7xw.qZFI-qEV9ACh142xs3MozA"
|
59
|
+
|
60
|
+
# Bob decrypts the cipher_text using the shared passphrase.
|
61
|
+
cipher_text = "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwicDJjIjo0MDk2LCJwMnMiOiJfZEllWDFGX29zUSJ9.B8vGlGGJBNSeGiaOKFJq3MXqhvK5fihL.WUt7m0TldTqW4eNb.wyjPCvfglCovqEd7xw.qZFI-qEV9ACh142xs3MozA"
|
62
|
+
jwk = JOSE::JWK.from_oct("alice and bob's secret")
|
63
|
+
plain_text, = jwk.block_decrypt(cipher_text)
|
64
|
+
# => "Hello, World!"
|
65
|
+
```
|
66
|
+
|
67
|
+
An even more complex combination might be `{"alg":"ECDH-ES","enc":"A128GCM"}` which requires two sets of keypairs. The sender (in our case Alice) uses their secret key and the public key of the receiver (Bob) to compute a shared key. The public key of the sender is embedded in the resulting encrypted message which the receiver then uses with their own secret key to decrypt the message.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# Let's setup the keys that will be used below (use JOSE::JWK.from_binary(...) to load the string version)
|
71
|
+
## Alice
|
72
|
+
alice_secret = JOSE::JWK.generate_key([:okp, :X25519])
|
73
|
+
# => "{\"crv\":\"X25519\",\"d\":\"0Pygq6jZVuWM4lPIhmCCbxtLIsWVzfnK5aM65PC1iX0\",\"kty\":\"OKP\",\"x\":\"uZGRsHIG33ebtKoIBG2WL4_TC_GTZtluSFuOmEPCngc\"}"
|
74
|
+
alice_public = alice_secret.to_public
|
75
|
+
# => "{\"crv\":\"X25519\",\"kty\":\"OKP\",\"x\":\"uZGRsHIG33ebtKoIBG2WL4_TC_GTZtluSFuOmEPCngc\"}"
|
76
|
+
## Bob
|
77
|
+
bob_secret = JOSE::JWK.generate_key([:okp, :X25519])
|
78
|
+
# => "{\"crv\":\"X25519\",\"d\":\"CMVqzIl-pHk0vuAUxhsZccYSLHpJfhRYvz3rTu6R8kc\",\"kty\":\"OKP\",\"x\":\"U-ckTk7roeu-9peZjdhZUIm9yJHtBrrkBJojCpUz5Cs\"}"
|
79
|
+
bob_public = bob_secret.to_public
|
80
|
+
# => "{\"crv\":\"X25519\",\"kty\":\"OKP\",\"x\":\"U-ckTk7roeu-9peZjdhZUIm9yJHtBrrkBJojCpUz5Cs\"}"
|
81
|
+
|
82
|
+
# Alice wants to send Bob an encrypted message.
|
83
|
+
# Bob first sends Alice his public key (bob_public above).
|
84
|
+
|
85
|
+
# Alice loads Bob's public key and encrypts the plain_text using her secret key.
|
86
|
+
# She then sends the resulting cipher_text to Bob.
|
87
|
+
plain_text = "Hello, World!"
|
88
|
+
bob_public = JOSE::JWK.from_binary("{\"crv\":\"X25519\",\"kty\":\"OKP\",\"x\":\"U-ckTk7roeu-9peZjdhZUIm9yJHtBrrkBJojCpUz5Cs\"}")
|
89
|
+
cipher_text = bob_public.box_encrypt(plain_text, alice_secret).compact
|
90
|
+
# => "eyJhbGciOiJFQ0RILUVTIiwiYXB1IjoiZTdwaGd3YU92NXVIVlFkOFJIRFRxcXpMYjZ6T0Nlb1oteXJLRTVmcTM2ayIsImFwdiI6IjY5U1Y3VWo0MWstMEhrcU1GOXVMaEk5Ty14TXZ0UlJ0bU0tbmxJV1RyV2MiLCJlbmMiOiJBMTI4R0NNIiwiZXBrIjp7ImNydiI6IlgyNTUxOSIsImt0eSI6Ik9LUCIsIngiOiJ1WkdSc0hJRzMzZWJ0S29JQkcyV0w0X1RDX0dUWnRsdVNGdU9tRVBDbmdjIn19..EeHOlLmlZocrf0Iz.O2kmN_-6m2YEWiR8XA.CTpSFJKy1GRLU3OoNj_AvA"
|
91
|
+
|
92
|
+
# Bob decrypts the cipher_text using his secret key.
|
93
|
+
cipher_text = "eyJhbGciOiJFQ0RILUVTIiwiYXB1IjoiZTdwaGd3YU92NXVIVlFkOFJIRFRxcXpMYjZ6T0Nlb1oteXJLRTVmcTM2ayIsImFwdiI6IjY5U1Y3VWo0MWstMEhrcU1GOXVMaEk5Ty14TXZ0UlJ0bU0tbmxJV1RyV2MiLCJlbmMiOiJBMTI4R0NNIiwiZXBrIjp7ImNydiI6IlgyNTUxOSIsImt0eSI6Ik9LUCIsIngiOiJ1WkdSc0hJRzMzZWJ0S29JQkcyV0w0X1RDX0dUWnRsdVNGdU9tRVBDbmdjIn19..EeHOlLmlZocrf0Iz.O2kmN_-6m2YEWiR8XA.CTpSFJKy1GRLU3OoNj_AvA"
|
94
|
+
plain_text, = bob_secret.box_decrypt(cipher_text)
|
95
|
+
# => "Hello, World!"
|
96
|
+
```
|
97
|
+
|
98
|
+
Here is an example of using X25519 and Ed25519 key pairs to encrypt and then sign a message.
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
# Alice's Signing Key Pair
|
102
|
+
alice_ed25519_secret = JOSE::JWS.generate_key({"alg" => "Ed25519"})
|
103
|
+
# => "{\"alg\":\"Ed25519\",\"crv\":\"Ed25519\",\"d\":\"CNnP7HYI-plw66s8GWOJwFCWWCZO1udseqEiyGxJGyk\",\"kty\":\"OKP\",\"use\":\"sig\",\"x\":\"atNiugZnSNxjmd5TM4eXg-aszq7Xarmpsuxrt2yIkUc\"}"
|
104
|
+
alice_ed25519_public = alice_ed25519_secret.to_public
|
105
|
+
# => "{\"alg\":\"Ed25519\",\"crv\":\"Ed25519\",\"kty\":\"OKP\",\"use\":\"sig\",\"x\":\"atNiugZnSNxjmd5TM4eXg-aszq7Xarmpsuxrt2yIkUc\"}"
|
106
|
+
|
107
|
+
# Bob's Key Agreement Key Pair
|
108
|
+
bob_x25519_secret = JOSE::JWK.generate_key([:okp, :X25519])
|
109
|
+
# => "{\"crv\":\"X25519\",\"d\":\"CMVqzIl-pHk0vuAUxhsZccYSLHpJfhRYvz3rTu6R8kc\",\"kty\":\"OKP\",\"x\":\"U-ckTk7roeu-9peZjdhZUIm9yJHtBrrkBJojCpUz5Cs\"}"
|
110
|
+
bob_x25519_public = bob_x25519_secret.to_public
|
111
|
+
# => "{\"crv\":\"X25519\",\"kty\":\"OKP\",\"x\":\"U-ckTk7roeu-9peZjdhZUIm9yJHtBrrkBJojCpUz5Cs\"}"
|
112
|
+
|
113
|
+
# Alice wants to send Bob an encrypted message that has also been signed.
|
114
|
+
# Alice provides Bob with her public signing key.
|
115
|
+
# Bob provides Alice with his public key agreement key.
|
116
|
+
# Alice does not specify her own key agreement key, which results in a new one being generated.
|
117
|
+
plain_text = "Hello, World!"
|
118
|
+
cipher_text, alice_x25519_secret = bob_x25519_public.box_encrypt(plain_text)
|
119
|
+
cipher_text = cipher_text.compact
|
120
|
+
# => ["eyJhbGciOiJFQ0RILUVTIiwiYXB1IjoiXy1leGtvYkpFQVpRWi01N1E2ZzRsMHNzbG0yT2I4TnRTeDlwcFFMZEJaNCIsImFwdiI6IjY5U1Y3VWo0MWstMEhrcU1GOXVMaEk5Ty14TXZ0UlJ0bU0tbmxJV1RyV2MiLCJlbmMiOiJBMTI4R0NNIiwiZXBrIjp7ImNydiI6IlgyNTUxOSIsImt0eSI6Ik9LUCIsIngiOiJQR3h4cXRXbnJ2Q0IwOVY0cVRoZEZpUUVweXp6ZVhEMVVJNjYyY0UxNHlrIn19..qcI2uruuJA7wZYda.-NhogJt2ofXlKSZMcQ.7Y9ZBHyPuuSj75u7zR7gpg",
|
121
|
+
# "{\"crv\":\"X25519\",\"d\":\"6Gu9vuKL_3YqjBrxLm2rcz3mkKNHMj5pkslVbL7XEVU\",\"kty\":\"OKP\",\"x\":\"PGxxqtWnrvCB09V4qThdFiQEpyzzeXD1UI662cE14yk\"}"]
|
122
|
+
|
123
|
+
# Alice then signs the cipher_text using her Ed25519 secret key.
|
124
|
+
signed_text = alice_ed25519_secret.sign(cipher_text).compact
|
125
|
+
# => "eyJhbGciOiJFZDI1NTE5In0.ZXlKaGJHY2lPaUpGUTBSSUxVVlRJaXdpWVhCMUlqb2lYeTFsZUd0dllrcEZRVnBSV2kwMU4xRTJaelJzTUhOemJHMHlUMkk0VG5SVGVEbHdjRkZNWkVKYU5DSXNJbUZ3ZGlJNklqWTVVMVkzVldvME1Xc3RNRWhyY1UxR09YVk1hRWs1VHkxNFRYWjBVbEowYlUwdGJteEpWMVJ5VjJNaUxDSmxibU1pT2lKQk1USTRSME5OSWl3aVpYQnJJanA3SW1OeWRpSTZJbGd5TlRVeE9TSXNJbXQwZVNJNklrOUxVQ0lzSW5naU9pSlFSM2g0Y1hSWGJuSjJRMEl3T1ZZMGNWUm9aRVpwVVVWd2VYcDZaVmhFTVZWSk5qWXlZMFV4TkhsckluMTkuLnFjSTJ1cnV1SkE3d1pZZGEuLU5ob2dKdDJvZlhsS1NaTWNRLjdZOVpCSHlQdXVTajc1dTd6UjdncGc.AK5wm7g9UjZflK5Z-0K7SRu8gPiT-zJoz0HBGy1fI9tnpw9_iXReqmsV0Z8NEa34gj4SZbSGYI7KZxXzyZ-VBw"
|
126
|
+
|
127
|
+
# Bob receives the signed_text and can immediately verify whether it's from Alice or not.
|
128
|
+
verified, cipher_text, = alice_ed25519_public.verify(signed_text)
|
129
|
+
if verified == true
|
130
|
+
# Bob can decrypt the cipher_text using his secret X25519 key.
|
131
|
+
plain_text, jwe = bob_x25519_secret.box_decrypt(cipher_text)
|
132
|
+
# => "Hello, World!"
|
133
|
+
# If Bob wanted to send a message back to Alice, he can use the embedded public key to do so:
|
134
|
+
jwe.alg.epk.box_encrypt("A message for Alice", bob_x25519_secret)
|
135
|
+
# At this point, however, we essentially have a partially functional SSL/TLS implementation.
|
136
|
+
end
|
137
|
+
```
|
@@ -0,0 +1,263 @@
|
|
1
|
+
# @title Key Generation
|
2
|
+
|
3
|
+
# Key Generation
|
4
|
+
|
5
|
+
There are four key generation methods described below for each key type:
|
6
|
+
|
7
|
+
* Method 1: OpenSSL
|
8
|
+
* Method 2: {JOSE::JWK.generate_key JOSE::JWK.generate_key}
|
9
|
+
* Method 3: {JOSE::JWE.generate_key JOSE::JWE.generate_key}
|
10
|
+
* Method 4: {JOSE::JWS.generate_key JOSE::JWS.generate_key}
|
11
|
+
|
12
|
+
## EC
|
13
|
+
|
14
|
+
The three curve types defined in the [JWA RFC 7518](https://tools.ietf.org/html/rfc7518#section-6.2.1.1) for the `EC` key type are:
|
15
|
+
|
16
|
+
1. `"P-256"` (openssl curve `secp256r1`)
|
17
|
+
2. `"P-384"` (openssl curve `secp384r1`)
|
18
|
+
3. `"P-521"` (openssl curve `secp521r1`)
|
19
|
+
|
20
|
+
### Method 1
|
21
|
+
|
22
|
+
The basic formula for key generation is `openssl ecparam -name CURVE -genkey -noout -out FILE`, for example:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
openssl ecparam -name secp256r1 -genkey -noout -out ec-secp256r1.pem
|
26
|
+
openssl ecparam -name secp384r1 -genkey -noout -out ec-secp384r1.pem
|
27
|
+
openssl ecparam -name secp521r1 -genkey -noout -out ec-secp521r1.pem
|
28
|
+
```
|
29
|
+
|
30
|
+
The PEM files can then be read using {JOSE::JWK.from_pem_file JOSE::JWK.from_pem_file}:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
jwk = JOSE::JWK.from_pem_file("ec-secp256r1.pem")
|
34
|
+
```
|
35
|
+
|
36
|
+
### Method 2
|
37
|
+
|
38
|
+
The curve names are almost the same as the ones for OpenSSL.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
jwk = JOSE::JWK.generate_key([:ec, 'prime256v1'])
|
42
|
+
jwk = JOSE::JWK.generate_key([:ec, 'secp384r1'])
|
43
|
+
jwk = JOSE::JWK.generate_key([:ec, 'secp521r1'])
|
44
|
+
|
45
|
+
# Alternative curve alias syntax:
|
46
|
+
jwk = JOSE::JWK.generate_key([:ec, 'P-256'])
|
47
|
+
jwk = JOSE::JWK.generate_key([:ec, 'P-384'])
|
48
|
+
jwk = JOSE::JWK.generate_key([:ec, 'P-521'])
|
49
|
+
```
|
50
|
+
|
51
|
+
Keys may also be generated based on other keys. The new key will use the same curve as the supplied key.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
old_jwk = JOSE::JWK.from_pem_file("ec-secp256r1.pem")
|
55
|
+
new_jwk = JOSE::JWK.generate_key(old_jwk)
|
56
|
+
```
|
57
|
+
|
58
|
+
### Method 3
|
59
|
+
|
60
|
+
If you have a JWE header with an `"epk"` field, a new key will be generated based on the same key type of the `"epk"`. Otherwise, the `P-521` curve will be used.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# Based on the "epk" field.
|
64
|
+
epk = JOSE::JWK.generate_key([:ec, 'P-256'])
|
65
|
+
jwe = JOSE::JWE.from_map({"alg" => "ECDH-ES", "enc" => "A128GCM", "epk" => epk.to_map})
|
66
|
+
jwk = jwe.generate_key
|
67
|
+
|
68
|
+
# Otherwise, defaults to "P-521".
|
69
|
+
jwk = JOSE::JWE.generate_key({"alg" => "ECDH-ES", "enc" => "A128GCM"})
|
70
|
+
```
|
71
|
+
|
72
|
+
### Method 4
|
73
|
+
|
74
|
+
If you have a JWS header with one of the ECDSA signature algorithms specified, a corresponding EC key will be generated with the correct curve for the signature type.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
jwk_ec256 = JOSE::JWS.generate_key({"alg" => "ES256"})
|
78
|
+
jwk_ec384 = JOSE::JWS.generate_key({"alg" => "ES384"})
|
79
|
+
jwk_ec521 = JOSE::JWS.generate_key({"alg" => "ES512"})
|
80
|
+
```
|
81
|
+
|
82
|
+
## oct
|
83
|
+
|
84
|
+
This key type is simply an octet or byte sequence (see [RFC 7518 Section 6.4](https://tools.ietf.org/html/rfc7518#section-6.4)).
|
85
|
+
|
86
|
+
### Method 1
|
87
|
+
|
88
|
+
The basic formula for generating a random octet sequence is `openssl rand -out FILE BYTE_SIZE`, for example:
|
89
|
+
|
90
|
+
```bash
|
91
|
+
openssl rand -out oct-128-bit.bin 16
|
92
|
+
```
|
93
|
+
|
94
|
+
The binary file can then be read using {JOSE::JWK.from_oct_file JOSE::JWK.from_oct_file}:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
jwk = JOSE::JWK.from_oct_file("oct-128-bit.bin")
|
98
|
+
```
|
99
|
+
|
100
|
+
### Method 2
|
101
|
+
|
102
|
+
Calling either of these functions with an integer will generate a random octet sequence.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
jwk = JOSE::JWK.generate_key([:oct, 16])
|
106
|
+
```
|
107
|
+
|
108
|
+
Keys may also be generated based on other keys. The new key will use the same byte size as the supplied key.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
old_jwk = JOSE::JWK.from_oct_file("oct-128-bit.bin")
|
112
|
+
new_jwk = JOSE::JWK.generate_key(old_jwk)
|
113
|
+
```
|
114
|
+
|
115
|
+
### Method 3
|
116
|
+
|
117
|
+
If you have a JWE header with an `"alg"` field that requires a symmetric key, a new `oct` key will be generated based on the byte size required of `"alg"` and/or `"enc"`.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
jwk_oct16 = JOSE::JWE.generate_key({"alg" => "dir", "enc" => "A128GCM"})
|
121
|
+
jwk_oct24 = JOSE::JWE.generate_key({"alg" => "dir", "enc" => "A192GCM"})
|
122
|
+
jwk_oct32 = JOSE::JWE.generate_key({"alg" => "dir", "enc" => "A256GCM"})
|
123
|
+
jwk_oct32 = JOSE::JWE.generate_key({"alg" => "dir", "enc" => "A128CBC-HS256"})
|
124
|
+
jwk_oct48 = JOSE::JWE.generate_key({"alg" => "dir", "enc" => "A192CBC-HS384"})
|
125
|
+
jwk_oct64 = JOSE::JWE.generate_key({"alg" => "dir", "enc" => "A256CBC-HS512"})
|
126
|
+
```
|
127
|
+
|
128
|
+
### Method 4
|
129
|
+
|
130
|
+
If you have a JWS header with an `"alg"` field that requires a symmetric key, a new `oct` key will be generated based on the byte size recommended for `"alg".
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
jwk_oct32 = JOSE::JWS.generate_key({"alg" => "HS256"})
|
134
|
+
jwk_oct48 = JOSE::JWS.generate_key({"alg" => "HS384"})
|
135
|
+
jwk_oct64 = JOSE::JWS.generate_key({"alg" => "HS512"})
|
136
|
+
```
|
137
|
+
|
138
|
+
## OKP
|
139
|
+
|
140
|
+
This key type is an octet key pair with an associated curve (see [draft-ietf-jose-cfrg-curves](https://tools.ietf.org/html/draft-ietf-jose-cfrg-curves)).
|
141
|
+
|
142
|
+
### Method 1
|
143
|
+
|
144
|
+
*NOTE:* Only `Ed25519` is currently supported by `ssh-keygen`.
|
145
|
+
|
146
|
+
The basic formula for generating a octet key pair is `ssh-keygen -t TYPE -f FILE`, for example:
|
147
|
+
|
148
|
+
```bash
|
149
|
+
ssh-keygen -t ed25519 -f ed25519
|
150
|
+
```
|
151
|
+
|
152
|
+
The private key file can then be read using {JOSE::JWK.from_openssh_key_file JOSE::JWK.from_openssh_key_file}:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
jwk = JOSE::JWK.from_openssh_key_file("ed25519")
|
156
|
+
```
|
157
|
+
|
158
|
+
### Method 2
|
159
|
+
|
160
|
+
Calling either of these functions with a specified curve will generate an octet key pair. You may also specify the secret portion of the key after the curve.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
# Curve25519
|
164
|
+
jwk_Ed25519 = JOSE::JWK.generate_key([:okp, :Ed25519])
|
165
|
+
jwk_Ed25519ph = JOSE::JWK.generate_key([:okp, :Ed25519ph])
|
166
|
+
jwk_X25519 = JOSE::JWK.generate_key([:okp, :X25519])
|
167
|
+
|
168
|
+
# Curve448
|
169
|
+
jwk_Ed448 = JOSE::JWK.generate_key([:okp, :Ed448])
|
170
|
+
jwk_Ed448ph = JOSE::JWK.generate_key([:okp, :Ed448ph])
|
171
|
+
jwk_X448 = JOSE::JWK.generate_key([:okp, :X448])
|
172
|
+
```
|
173
|
+
|
174
|
+
Keys may also be generated based on other keys. The new key will use the same curve as the supplied key.
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
old_jwk = JOSE::JWK.from_openssh_key_file("ed25519")
|
178
|
+
new_jwk = JOSE::JWK.generate_key(old_jwk)
|
179
|
+
```
|
180
|
+
|
181
|
+
### Method 3
|
182
|
+
|
183
|
+
If you have a JWE header with an `"epk"` field, a new key will be generated based on the same key type of the `"epk"`.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
# Based on the "epk" field.
|
187
|
+
epk = JOSE::JWK.generate_key([:okp, :X25519])
|
188
|
+
jwe = JOSE::JWE.from_map({"alg" => "ECDH-ES", "enc" => "A128GCM", "epk" => epk.to_map})
|
189
|
+
jwk = jwe.generate_key
|
190
|
+
```
|
191
|
+
|
192
|
+
### Method 4
|
193
|
+
|
194
|
+
If you have a JWS header with one of the EdDSA signature algorithms specified, a corresponding OKP key will be generated with the correct curve for the signature type.
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
jwk_Ed25519 = JOSE::JWS.generate_key({"alg" => "Ed25519"})
|
198
|
+
jwk_Ed25519ph = JOSE::JWS.generate_key({"alg" => "Ed25519ph"})
|
199
|
+
jwk_Ed448 = JOSE::JWS.generate_key({"alg" => "Ed448"})
|
200
|
+
jwk_Ed448ph = JOSE::JWS.generate_key({"alg" => "Ed448ph"})
|
201
|
+
```
|
202
|
+
|
203
|
+
## RSA
|
204
|
+
|
205
|
+
Both two-prime and multi-prime RSA keys are supported by [RFC 7518 Section 6.3](https://tools.ietf.org/html/rfc7518#section-6.3), but currently only two-prime RSA keys can be generated by OpenSSL-based generators. Ruby does not support multi-prime RSA at this time.
|
206
|
+
|
207
|
+
### Method 1
|
208
|
+
|
209
|
+
The basic formula for generating a RSA key is `openssl genrsa -out FILE BIT_SIZE`, for example:
|
210
|
+
|
211
|
+
```bash
|
212
|
+
openssl genrsa -out rsa-2048.pem 2048
|
213
|
+
```
|
214
|
+
|
215
|
+
The PEM file can then be read using {JOSE::JWK.from_pem_file JOSE::JWK.from_pem_file}:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
jwk = JOSE::JWK.from_pem_file("rsa-2048.pem")
|
219
|
+
```
|
220
|
+
|
221
|
+
### Method 2
|
222
|
+
|
223
|
+
The modulus bit size is the only required argument. Optionally, you may specify the public exponent as the second argument (default is `65537`).
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
jwk = JOSE::JWK.generate_key([:rsa, 2048])
|
227
|
+
|
228
|
+
# Alternative explicit syntax with public exponent:
|
229
|
+
jwk = JOSE::JWK.generate_key([:rsa, 4096, 65537])
|
230
|
+
```
|
231
|
+
|
232
|
+
Keys may also be generated based on other keys. The new key will use the same modulus size and public exponent as the supplied key.
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
old_jwk = JOSE::JWK.from_pem_file("rsa-2048.pem")
|
236
|
+
new_jwk = JOSE::JWK.generate_key(old_jwk)
|
237
|
+
```
|
238
|
+
|
239
|
+
### Method 3
|
240
|
+
|
241
|
+
If you have a JWE header with an `"alg"` field that requires an asymmetric RSA key, a new `RSA` key will be generated. 2048-bit keys are generated in these cases.
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
jwk_rsa1_5 = JOSE::JWE.generate_key({"alg" => "RSA1_5", "enc" => "A128GCM"})
|
245
|
+
jwk_rsa_oaep = JOSE::JWE.generate_key({"alg" => "RSA-OAEP", "enc" => "A128GCM"})
|
246
|
+
jwk_rsa_oaep256 = JOSE::JWE.generate_key({"alg" => "RSA-OAEP-256", "enc" => "A128GCM"})
|
247
|
+
```
|
248
|
+
|
249
|
+
### Method 4
|
250
|
+
|
251
|
+
If you have a JWS header with one of the RSA PKCS1 or PSS signature algorithms specified, a corresponding RSA key will be generated with a recommended modulus size based on the digest type.
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
# RS256, RS384, RS512
|
255
|
+
jwk_rsa2048 = JOSE::JWS.generate_key({"alg" => "RS256"})
|
256
|
+
jwk_rsa3072 = JOSE::JWS.generate_key({"alg" => "RS384"})
|
257
|
+
jwk_rsa4096 = JOSE::JWS.generate_key({"alg" => "RS512"})
|
258
|
+
|
259
|
+
# PS256, PS384, PS512
|
260
|
+
jwk_rsa2048 = JOSE::JWS.generate_key({"alg" => "PS256"})
|
261
|
+
jwk_rsa3072 = JOSE::JWS.generate_key({"alg" => "PS384"})
|
262
|
+
jwk_rsa4096 = JOSE::JWS.generate_key({"alg" => "PS512"})
|
263
|
+
```
|