Class: Azure::Certificate

Inherits:
Object
  • Object
show all
Defined in:
lib/azure/service_management/certificate.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection) ⇒ Certificate

Returns a new instance of Certificate.



53
54
55
56
# File 'lib/azure/service_management/certificate.rb', line 53

def initialize(connection)
  @connection = connection
  @certificate_version = 2 # cf. RFC 5280 - to make it a "v3" certificate
end

Instance Attribute Details

#cert_dataObject

Returns the value of attribute cert_data.



52
53
54
# File 'lib/azure/service_management/certificate.rb', line 52

def cert_data
  @cert_data
end

#certificate_versionObject

Returns the value of attribute certificate_version.



52
53
54
# File 'lib/azure/service_management/certificate.rb', line 52

def certificate_version
  @certificate_version
end

#connectionObject

Returns the value of attribute connection.



51
52
53
# File 'lib/azure/service_management/certificate.rb', line 51

def connection
  @connection
end

#fingerprintObject

Returns the value of attribute fingerprint.



52
53
54
# File 'lib/azure/service_management/certificate.rb', line 52

def fingerprint
  @fingerprint
end

Instance Method Details

#add_certificate(certificate_data, certificate_password, certificate_format, dns_name) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/azure/service_management/certificate.rb', line 97

def add_certificate(certificate_data, certificate_password, certificate_format, dns_name)
  # Generate XML to call the API
  # Add certificate to the hosted service
  builder = Nokogiri::XML::Builder.new do |xml|
    xml.CertificateFile("xmlns" => "http://schemas.microsoft.com/windowsazure") do
      xml.Data certificate_data
      xml.CertificateFormat certificate_format
      xml.Password certificate_password
    end
  end
  # Windows Azure API call
  @connection.query_azure("hostedservices/#{dns_name}/certificates", "post", builder.to_xml)

  # Check if certificate is available else raise error
  for attempt in 0..4
    Chef::Log.info "Waiting to get certificate ..."
    res = get_certificate(dns_name, @fingerprint)
    break unless res.empty?
    if attempt == 4
      raise "The certificate with thumbprint #{fingerprint} was not found."
    else
      sleep 5
    end
  end
end

#create(params) ⇒ Object



58
59
60
61
62
63
64
65
66
67
# File 'lib/azure/service_management/certificate.rb', line 58

def create(params)
  # If RSA private key has been specified, then generate an x 509 certificate from the
  # public part of the key
  @cert_data = generate_public_key_certificate_data({ ssh_key: params[:ssh_identity_file],
                                                      ssh_key_passphrase: params[:identity_file_passphrase] })
  add_certificate @cert_data, "knifeazure", "pfx", params[:azure_dns_name]

  # Return the fingerprint to be used while adding role
  @fingerprint
end

#create_ssl_certificate(cert_params) ⇒ Object

SSL certificate generation for knife-azure ssl bootstrap ######



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/azure/service_management/certificate.rb', line 128

def create_ssl_certificate(cert_params)
  file_path = cert_params[:output_file].sub(/\.(\w+)$/, "")
  path = prompt_for_file_path
  file_path = File.join(path, file_path) unless path.empty?
  cert_params[:domain] = prompt_for_domain

  rsa_key = generate_keypair cert_params[:key_length]
  cert = generate_certificate(rsa_key, cert_params)
  write_certificate_to_file cert, file_path, rsa_key, cert_params
  puts "*" * 70
  puts "Generated Certificates:"
  puts "- #{file_path}.pfx - PKCS12 format keypair. Contains both the public and private keys, usually used on the server."
  puts "- #{file_path}.b64 - Base64 encoded PKCS12 keypair. Contains both the public and private keys, for upload to the Azure REST API."
  puts "- #{file_path}.pem - Base64 encoded public certificate only. Required by the client to connect to the server."
  puts "Certificate Thumbprint: #{@thumbprint.to_s.upcase}"
  puts "*" * 70

  Chef::Config[:knife][:ca_trust_file] = file_path + ".pem" if Chef::Config[:knife][:ca_trust_file].nil?
  cert_data = File.read (file_path + ".b64")
  add_certificate cert_data, @winrm_cert_passphrase, "pfx", cert_params[:azure_dns_name]
  @thumbprint
end

#generate_certificate(rsa_key, cert_params) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/azure/service_management/certificate.rb', line 197

def generate_certificate(rsa_key, cert_params)
  @hostname = "*"
  if cert_params[:domain]
    @hostname = "*." + cert_params[:domain]
  end

  # Create a self-signed X509 certificate from the rsa_key (unencrypted)
  cert = OpenSSL::X509::Certificate.new
  cert.version = 2
  cert.serial = Random.rand(65534) + 1 # 2 digit byte range random number for better security aspect

  cert.subject = OpenSSL::X509::Name.parse "/CN=#{@hostname}"
  cert.issuer = cert.subject
  cert.public_key = rsa_key.public_key
  cert.not_before = Time.now
  cert.not_after = cert.not_before + 2 * 365 * cert_params[:cert_validity].to_i * 60 * 60 # 2 years validity
  ef = OpenSSL::X509::ExtensionFactory.new
  ef.subject_certificate = cert
  ef.issuer_certificate = cert
  cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
  cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
  cert.add_extension(ef.create_extension("extendedKeyUsage", "1.3.6.1.5.5.7.3.1", false))
  cert.sign(rsa_key, OpenSSL::Digest::SHA1.new)
  @thumbprint = OpenSSL::Digest::SHA1.new(cert.to_der)
  cert
end

#generate_keypair(key_length) ⇒ Object



151
152
153
# File 'lib/azure/service_management/certificate.rb', line 151

def generate_keypair(key_length)
  OpenSSL::PKey::RSA.new(key_length.to_i)
end

#generate_public_key_certificate_data(params) ⇒ Object



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
# File 'lib/azure/service_management/certificate.rb', line 69

def generate_public_key_certificate_data(params)
  # Generate OpenSSL RSA key from the mentioned ssh key path (and passphrase)
  key = OpenSSL::PKey::RSA.new(File.read(params[:ssh_key]), params[:ssh_key_passphrase])
  # Generate X 509 certificate
  ca = OpenSSL::X509::Certificate.new
  ca.version = @certificate_version
  ca.serial = Random.rand(65534) + 1 # 2 digit byte range random number for better security aspect
  ca.subject = OpenSSL::X509::Name.parse "/DC=org/DC=knife-plugin/CN=Opscode CA"
  ca.issuer = ca.subject # root CA's are "self-signed"
  ca.public_key = key.public_key # Assign the ssh-key's public part to the certificate
  ca.not_before = Time.now
  ca.not_after =  ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
  ef = OpenSSL::X509::ExtensionFactory.new
  ef.subject_certificate = ca
  ef.issuer_certificate = ca
  ca.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
  ca.add_extension(ef.create_extension("keyUsage", "keyCertSign, cRLSign", true))
  ca.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
  ca.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
  ca.sign(key, OpenSSL::Digest::SHA256.new)
  # Generate the SHA1 fingerprint of the der format of the X 509 certificate
  @fingerprint = OpenSSL::Digest::SHA1.new(ca.to_der)
  # Create the pfx format of the certificate
  pfx = OpenSSL::PKCS12.create("knifeazure", "knife-azure-pfx", key, ca)
  # Encode the pfx format - upload this certificate
  Base64.strict_encode64(pfx.to_der)
end

#get_certificate(dns_name, fingerprint) ⇒ Object



123
124
125
# File 'lib/azure/service_management/certificate.rb', line 123

def get_certificate(dns_name, fingerprint)
  @connection.query_azure("hostedservices/#{dns_name}/certificates/sha1-#{fingerprint}", "get").search("Certificate")
end

#prompt_for_domainObject



185
186
187
188
189
190
191
192
193
194
195
# File 'lib/azure/service_management/certificate.rb', line 185

def prompt_for_domain
  counter = 0
  begin
    print "Enter the domain (mandatory):"
    domain = STDIN.gets
    domain = domain.strip
    counter += 1
    exit(1) if counter == 3
  end until !domain.empty?
  domain
end

#prompt_for_file_pathObject



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/azure/service_management/certificate.rb', line 169

def prompt_for_file_path
  file_path = ""
  counter = 0
  begin
    print "Invalid location! \n" unless file_path.empty?
    print 'Enter the file path for certificates e.g. C:\Windows (empty for current location):'
    file_path = STDIN.gets
    stripped_file_path = file_path.strip
    return stripped_file_path if file_path == "\n"

    counter += 1
    exit(1) if counter == 3
  end until File.directory?(stripped_file_path)
  stripped_file_path
end

#prompt_for_passphraseObject



155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/azure/service_management/certificate.rb', line 155

def prompt_for_passphrase
  passphrase = ""
  begin
    print "Passphrases do not match.  Try again.\n" unless passphrase.empty?
    print "Enter certificate passphrase (empty for no passphrase):"
    passphrase = STDIN.gets
    return passphrase.strip if passphrase == "\n"

    print "Enter same passphrase again:"
    confirm_passphrase = STDIN.gets
  end until passphrase == confirm_passphrase
  passphrase.strip
end

#write_certificate_to_file(cert, file_path, rsa_key, cert_params) ⇒ Object



224
225
226
227
228
229
230
# File 'lib/azure/service_management/certificate.rb', line 224

def write_certificate_to_file(cert, file_path, rsa_key, cert_params)
  File.open(file_path + ".pem", "wb") { |f| f.print cert.to_pem }
  @winrm_cert_passphrase = prompt_for_passphrase unless @winrm_cert_passphrase
  pfx = OpenSSL::PKCS12.create("#{cert_params[:winrm_cert_passphrase]}", "winrmcert", rsa_key, cert)
  File.open(file_path + ".pfx", "wb") { |f| f.print pfx.to_der }
  File.open(file_path + ".b64", "wb") { |f| f.print Base64.strict_encode64(pfx.to_der) }
end