summaryrefslogtreecommitdiff
path: root/vendor/acme-client/lib/acme/client/crypto.rb
blob: dfa5cdc935037ad44f23763742e7fe777362e834 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class Acme::Client::Crypto
  attr_reader :private_key

  def initialize(private_key)
    @private_key = private_key
  end

  def generate_signed_jws(header:, payload:)
    header = { typ: 'JWT', alg: jws_alg, jwk: jwk }.merge(header)

    encoded_header = urlsafe_base64(header.to_json)
    encoded_payload = urlsafe_base64(payload.to_json)
    signature_data = "#{encoded_header}.#{encoded_payload}"

    signature = private_key.sign digest, signature_data
    encoded_signature = urlsafe_base64(signature)

    {
      protected: encoded_header,
      payload: encoded_payload,
      signature: encoded_signature
    }.to_json
  end

  def thumbprint
    urlsafe_base64 digest.digest(jwk.to_json)
  end

  def digest
    OpenSSL::Digest::SHA256.new
  end

  def urlsafe_base64(data)
    Base64.urlsafe_encode64(data).sub(/[\s=]*\z/, '')
  end

  private

  def jws_alg
    { 'RSA' => 'RS256', 'EC' => 'ES256' }.fetch(jwk[:kty])
  end

  def jwk
    @jwk ||= case private_key
             when OpenSSL::PKey::RSA
               rsa_jwk
             when OpenSSL::PKey::EC
               ec_jwk
             else
               raise ArgumentError, "Can't handle #{private_key} as private key, only OpenSSL::PKey::RSA and OpenSSL::PKey::EC"
    end
  end

  def rsa_jwk
    {
      e: urlsafe_base64(public_key.e.to_s(2)),
      kty: 'RSA',
      n: urlsafe_base64(public_key.n.to_s(2))
    }
  end

  def ec_jwk
    {
      crv: curve_name,
      kty: 'EC',
      x: urlsafe_base64(coordinates[:x].to_s(2)),
      y: urlsafe_base64(coordinates[:y].to_s(2))
    }
  end

  def curve_name
    {
      'prime256v1' => 'P-256',
      'secp384r1' => 'P-384',
      'secp521r1' => 'P-521'
    }.fetch(private_key.group.curve_name) { raise ArgumentError, 'Unknown EC curve' }
  end

  # rubocop:disable Metrics/AbcSize
  def coordinates
    @coordinates ||= begin
      hex = public_key.to_bn.to_s(16)
      data_len = hex.length - 2
      hex_x = hex[2, data_len / 2]
      hex_y = hex[2 + data_len / 2, data_len / 2]

      {
        x: OpenSSL::BN.new([hex_x].pack('H*'), 2),
        y: OpenSSL::BN.new([hex_y].pack('H*'), 2)
      }
    end
  end
  # rubocop:enable Metrics/AbcSize

  def public_key
    @public_key ||= private_key.public_key
  end
end