Class: Slosilo::Key
- Inherits:
-
Object
- Object
- Slosilo::Key
- Defined in:
- lib/slosilo/key.rb
Constant Summary collapse
- SIGNATURE_LEN =
256
- JWT_ALGORITHM =
'conjur.org/slosilo/v2'.freeze
- DEFAULT_EXPIRATION =
8 * 60
Instance Attribute Summary collapse
-
#key ⇒ Object
readonly
Returns the value of attribute key.
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
- #cipher ⇒ Object
- #decrypt(ciphertext, skey) ⇒ Object
- #decrypt_message(ciphertext) ⇒ Object
- #encrypt(plaintext) ⇒ Object
- #encrypt_message(plaintext) ⇒ Object
- #err(msg) ⇒ Object
- #fingerprint ⇒ Object
- #hash ⇒ Object
-
#initialize(raw_key = nil) ⇒ Key
constructor
A new instance of Key.
-
#issue_jwt(claims) ⇒ Object
Issue a JWT with the given claims.
-
#jwt_valid?(token) ⇒ Boolean
Validate a JWT.
-
#private? ⇒ Boolean
checks if the keypair contains a private key.
-
#public ⇒ Object
return a new key with just the public part of this.
- #sign(value) ⇒ Object
- #sign_string(value) ⇒ Object
-
#signed_token(data) ⇒ Object
create a new timestamped and signed token carrying data.
- #to_der ⇒ Object
- #to_s ⇒ Object
- #token_valid?(token, expiry = DEFAULT_EXPIRATION) ⇒ Boolean
-
#validate_jwt(token) ⇒ Object
Validate a JWT.
- #verify_signature(data, signature) ⇒ Object
Constructor Details
#initialize(raw_key = nil) ⇒ Key
Returns a new instance of Key.
10 11 12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/slosilo/key.rb', line 10 def initialize raw_key = nil @key = if raw_key.is_a? OpenSSL::PKey::RSA raw_key elsif !raw_key.nil? OpenSSL::PKey.read raw_key else OpenSSL::PKey::RSA.new 2048 end rescue OpenSSL::PKey::PKeyError => e # old openssl versions used to report ArgumentError # which arguably makes more sense here, so reraise as that raise ArgumentError, e, e.backtrace end |
Instance Attribute Details
#key ⇒ Object (readonly)
Returns the value of attribute key.
24 25 26 |
# File 'lib/slosilo/key.rb', line 24 def key @key end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
164 165 166 |
# File 'lib/slosilo/key.rb', line 164 def == other to_der == other.to_der end |
#cipher ⇒ Object
26 27 28 |
# File 'lib/slosilo/key.rb', line 26 def cipher @cipher ||= Slosilo::Symmetric.new end |
#decrypt(ciphertext, skey) ⇒ Object
42 43 44 45 |
# File 'lib/slosilo/key.rb', line 42 def decrypt ciphertext, skey key = @key.private_decrypt skey cipher.decrypt ciphertext, key: key end |
#decrypt_message(ciphertext) ⇒ Object
47 48 49 50 |
# File 'lib/slosilo/key.rb', line 47 def ciphertext k, c = ciphertext.unpack("A256A*") decrypt c, k end |
#encrypt(plaintext) ⇒ Object
30 31 32 33 34 35 |
# File 'lib/slosilo/key.rb', line 30 def encrypt plaintext key = cipher.random_key ctxt = cipher.encrypt plaintext, key: key key = @key.public_encrypt key [ctxt, key] end |
#encrypt_message(plaintext) ⇒ Object
37 38 39 40 |
# File 'lib/slosilo/key.rb', line 37 def plaintext c, k = encrypt plaintext k + c end |
#err(msg) ⇒ Object
141 142 143 |
# File 'lib/slosilo/key.rb', line 141 def err msg raise Error::TokenValidationError, msg, caller end |
#fingerprint ⇒ Object
160 161 162 |
# File 'lib/slosilo/key.rb', line 160 def fingerprint @fingerprint ||= OpenSSL::Digest::SHA256.hexdigest key.public_key.to_der end |
#hash ⇒ Object
170 171 172 |
# File 'lib/slosilo/key.rb', line 170 def hash to_der.hash end |
#issue_jwt(claims) ⇒ Object
Issue a JWT with the given claims. ‘iat` (issued at) claim is automatically added. Other interesting claims you can give are:
-
‘sub` - token subject, for example a user name;
-
‘exp` - expiration time (absolute);
-
‘cidr` (Conjur extension) - array of CIDR masks that are accepted to make requests that bear this token
90 91 92 93 94 95 96 97 |
# File 'lib/slosilo/key.rb', line 90 def issue_jwt claims token = Slosilo::JWT.new claims token.add_signature \ alg: JWT_ALGORITHM, kid: fingerprint, &method(:sign) token.freeze end |
#jwt_valid?(token) ⇒ Boolean
Validate a JWT.
Convenience method calling #validate_jwt and returning false if an exception is raised.
117 118 119 120 121 122 |
# File 'lib/slosilo/key.rb', line 117 def jwt_valid? token validate_jwt token true rescue false end |
#private? ⇒ Boolean
checks if the keypair contains a private key
180 181 182 |
# File 'lib/slosilo/key.rb', line 180 def private? @key.private? end |
#public ⇒ Object
return a new key with just the public part of this
175 176 177 |
# File 'lib/slosilo/key.rb', line 175 def public Key.new(@key.public_key) end |
#sign(value) ⇒ Object
60 61 62 |
# File 'lib/slosilo/key.rb', line 60 def sign value sign_string(stringify value) end |
#sign_string(value) ⇒ Object
155 156 157 158 |
# File 'lib/slosilo/key.rb', line 155 def sign_string value salt = shake_salt key.private_encrypt(hash_function.digest(salt + value)) + salt end |
#signed_token(data) ⇒ Object
create a new timestamped and signed token carrying data
74 75 76 77 78 79 |
# File 'lib/slosilo/key.rb', line 74 def signed_token data token = { "data" => data, "timestamp" => Time.new.utc.to_s } token["signature"] = Base64::urlsafe_encode64(sign token) token["key"] = fingerprint token end |
#to_der ⇒ Object
56 57 58 |
# File 'lib/slosilo/key.rb', line 56 def to_der @to_der ||= @key.to_der end |
#to_s ⇒ Object
52 53 54 |
# File 'lib/slosilo/key.rb', line 52 def to_s @key.public_key.to_pem end |
#token_valid?(token, expiry = DEFAULT_EXPIRATION) ⇒ Boolean
101 102 103 104 105 106 107 108 |
# File 'lib/slosilo/key.rb', line 101 def token_valid? token, expiry = DEFAULT_EXPIRATION return jwt_valid? token if token.respond_to? :header token = token.clone expected_key = token.delete "key" return false if (expected_key and (expected_key != fingerprint)) signature = Base64::urlsafe_decode64(token.delete "signature") (Time.parse(token["timestamp"]) + expiry > Time.now) && verify_signature(token, signature) end |
#validate_jwt(token) ⇒ Object
It’s the responsibility of the caller to examine other claims
Validate a JWT.
First checks whether algorithm is ‘conjur.org/slosilo/v2’ and the key id matches this key’s fingerprint. Then verifies if the token is not expired, as indicated by the ‘exp` claim; in its absence tokens are assumed to expire in `iat` + 8 minutes.
If those checks pass, finally the signature is verified.
included in the token; consideration needs to be given to handling unrecognized claims.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/slosilo/key.rb', line 140 def validate_jwt token def err msg raise Error::TokenValidationError, msg, caller end header = token.header err 'unrecognized algorithm' unless header['alg'] == JWT_ALGORITHM err 'mismatched key' if (kid = header['kid']) && kid != fingerprint iat = Time.at token.claims['iat'] || err('unknown issuing time') exp = Time.at token.claims['exp'] || (iat + DEFAULT_EXPIRATION) err 'token expired' if exp <= Time.now err 'invalid signature' unless verify_signature token.string_to_sign, token.signature true end |
#verify_signature(data, signature) ⇒ Object
66 67 68 69 70 71 |
# File 'lib/slosilo/key.rb', line 66 def verify_signature data, signature signature, salt = signature.unpack("a#{SIGNATURE_LEN}a*") key.public_decrypt(signature) == hash_function.digest(salt + stringify(data)) rescue false end |