msidp-endpoint 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c3063389a5690ccaabdb90a88d5b829e4d679c75535e557abcd2894b5b7423e
4
- data.tar.gz: 496d1e0b37bd567c2c955f716360e451b8ce768454095a3746364a423c1ee2ec
3
+ metadata.gz: 37385ee2c6bd0f32d4cc2a176e7140ba05a1f756a97e5539c963dcb1b4a9c075
4
+ data.tar.gz: d586b59889dbe8b58f4aaeba41abc5dc52fbbd07ed06367607c0b2bd703b603a
5
5
  SHA512:
6
- metadata.gz: 7f70b2959ca814ec3d3c967b46183b5f79d3c9ccf642a30929b741d6321f22da91b918b5937ec943086c94cf8647108916b515674c44e27213d35d6cf3f9d93b
7
- data.tar.gz: c6f5d2f9e19039707aad84c62f7b863d4b93b2c03ea21a4a4208ec6571ecddd825b8659eccec51aff3b7ad137b3357b9e2f49dd641781bf96318679175737897
6
+ metadata.gz: 7c7cdd5a0bad6a7da2ce4f43c63683c8b9d81308053490bdd1cc842195f6f1d7bc2dc02b1d65c6bb1009fc2ed734444c8e3007dcb9b6793edc6a51c5796b80f3
7
+ data.tar.gz: 7efc055c1962d6a7eeda5742cb8845303f4ea728ec30fd103d291748a603c99d7412843d7eb832527c564631a3e585cee18fb8fbe98105c94de8f899f1351309
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'msidp/certificate_credential'
5
+
6
+ if ARGV.length < 4
7
+ progname = File.basename($PROGRAM_NAME)
8
+ warn <<~USAGE
9
+ Compute a client assetion for a certificate pair.
10
+ Usage:
11
+ #{progname} certificate_file private_key_file tenant_id client_id
12
+ certificate_file: public certificate file
13
+ private_key_file: private key file
14
+ tenant_id: tenant ID in GUID or domain-name format
15
+ client_id: applicaiton (client) ID
16
+ USAGE
17
+ exit 64
18
+ end
19
+
20
+ certificate, private_key, tenant_id, client_id = ARGV
21
+
22
+ cert = OpenSSL::X509::Certificate.new(File.binread(certificate))
23
+ key = OpenSSL::PKey::RSA.new(File.binread(private_key))
24
+
25
+ cred = MSIDP::CertificateCredential.new(
26
+ cert, key, tenant_id: tenant_id, client_id: client_id
27
+ )
28
+
29
+ puts cred.assertion
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+ require 'json'
6
+
7
+ module MSIDP
8
+ # Certificate credential for application authentication
9
+ class CertificateCredential
10
+ # @return [String] tenant a directory tenant in GUID or domain-name format.
11
+ attr_accessor :tenant
12
+ # @return [String] client_id the assigned applicaiton (client) ID.
13
+ attr_accessor :client_id
14
+
15
+ # Initialize an instance
16
+ #
17
+ # @param [OpenSSL::X509::Certificate] cert a certificate.
18
+ # @param [OpenSSL::PKey] cert the private key paired with the certificate.
19
+ # @param [String] tenant a directory tenant in GUID or domain-name format.
20
+ # @param [String] client_id the assigned applicaiton (client) ID.
21
+ def initialize(cert, key, tenant:, client_id:)
22
+ @cert = cert
23
+ @key = key
24
+ @tenant = tenant
25
+ @client_id = client_id
26
+ end
27
+
28
+ # Computes the JWT assertion.
29
+ #
30
+ # @return [String] JWT assertion string.
31
+ def assertion
32
+ header_base64 = base64url_encode(header)
33
+ payload_base64 = base64url_encode(payload)
34
+ signature = @key.sign('sha256', "#{header_base64}.#{payload_base64}")
35
+ sign_base64 = base64url_encode(signature)
36
+ "#{header_base64}.#{payload_base64}.#{sign_base64}"
37
+ end
38
+
39
+ # JOSE header of the JWT.
40
+ #
41
+ # @return [String] JSON string of the JOSE header.
42
+ def header
43
+ digest = OpenSSL::Digest::SHA1.digest(@cert.to_der)
44
+ x5t = Base64.urlsafe_encode64(digest)
45
+ header = { alg: 'RS256', typ: 'JWT', x5t: x5t.to_s }
46
+ JSON.dump(header)
47
+ end
48
+
49
+ # JWS payload of the JWT claim.
50
+ #
51
+ # @return [String] JSON string of the JWS payload.
52
+ def payload
53
+ not_after = @cert.not_after.to_i
54
+ not_before = @cert.not_before.to_i
55
+ jti = make_jwt_id
56
+ payload = {
57
+ aud: "https://login.microsoftonline.com/#{tenant}/v2.0",
58
+ exp: not_after, iss: client_id, jti: jti,
59
+ nbf: not_after, sub: client_id, iat: not_before
60
+ }
61
+ JSON.dump(payload)
62
+ end
63
+
64
+ private
65
+
66
+ def base64url_encode(data)
67
+ Base64.urlsafe_encode64(data, padding: false)
68
+ end
69
+
70
+ def make_jwt_id
71
+ data = @cert.to_der
72
+ data << tenant
73
+ data << client_id
74
+ sha1hash_uuid(OpenSSL::Digest::SHA1.digest(data))
75
+ end
76
+
77
+ # rubocop:disable Style/FormatStringToken
78
+ def sha1hash_uuid(hash)
79
+ bytes = hash.bytes[0..15]
80
+ bytes[6] = bytes[6] & 0x0F | 0x50
81
+ bytes[8] = bytes[6] & 0x3F | 0x80
82
+ format(
83
+ '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x',
84
+ *bytes
85
+ )
86
+ end
87
+ # rubocop:enable Style/FormatStringToken
88
+ end
89
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module MSIDP
4
4
  module Endpoint
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,25 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: msidp-endpoint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SHIMAYOSHI, Takao
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-18 00:00:00.000000000 Z
11
+ date: 2022-03-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Simple class library for Microsoft Identity Platform endpoints.
14
14
  email:
15
15
  - simayosi@cc.kyushu-u.ac.jp
16
- executables: []
16
+ executables:
17
+ - msidp-cert2assertion
17
18
  extensions: []
18
19
  extra_rdoc_files: []
19
20
  files:
20
21
  - LICENSE.txt
21
22
  - README.md
23
+ - exe/msidp-cert2assertion
22
24
  - lib/msidp/access_token.rb
25
+ - lib/msidp/certificate_credential.rb
23
26
  - lib/msidp/endpoint.rb
24
27
  - lib/msidp/endpoint/version.rb
25
28
  - lib/msidp/error.rb