Class: Redwood::CryptoManager

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/sup/crypto.rb

Defined Under Namespace

Classes: Error

Constant Summary

OUTGOING_MESSAGE_OPERATIONS =
OrderedHash.new(
  [:sign, "Sign"],
  [:sign_and_encrypt, "Sign and encrypt"],
  [:encrypt, "Encrypt only"]
)

Instance Method Summary collapse

Constructor Details

#initializeCryptoManager

Returns a new instance of CryptoManager



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/sup/crypto.rb', line 14

def initialize
  @mutex = Mutex.new
  self.class.i_am_the_instance self

  bin = `which gpg`.chomp

  @cmd =
    case bin
    when /\S/
      Redwood::log "crypto: detected gpg binary in #{bin}"
      "#{bin} --quiet --batch --no-verbose --logger-fd 1 --use-agent"
    else
      Redwood::log "crypto: no gpg binary detected"
      nil
    end
end

Instance Method Details

#decrypt(payload) ⇒ Object

returns decrypted_message, status, desc, lines



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/sup/crypto.rb', line 109

def decrypt payload # a RubyMail::Message object
  return unknown_status(cant_find_binary) unless @cmd

  payload_fn = Tempfile.new "redwood.payload"
  payload_fn.write payload.to_s
  payload_fn.close

  output = run_gpg "--decrypt #{payload_fn.path}"

  if $?.success?
    decrypted_payload, sig_lines =
      if output =~ /\A(.*?)((^gpg: .*$)+)\Z/m
        [$1, $2]
      else
        [output, nil]
      end
    
    sig = 
      if sig_lines # encrypted & signed
        if sig_lines =~ /^gpg: (Good signature from .*$)/
          Chunk::CryptoNotice.new :valid, $1, sig_lines.split("\n")
        else
          Chunk::CryptoNotice.new :invalid, $1, sig_lines.split("\n")
        end
      end

    notice = Chunk::CryptoNotice.new :valid, "This message has been decrypted for display"
    [RMail::Parser.read(decrypted_payload), sig, notice]
  else
    notice = Chunk::CryptoNotice.new :invalid, "This message could not be decrypted", output.split("\n")
    [nil, nil, notice]
  end
end

#encrypt(from, to, payload, sign = false) ⇒ Object

Raises:



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
# File 'lib/sup/crypto.rb', line 51

def encrypt from, to, payload, sign=false
  payload_fn = Tempfile.new "redwood.payload"
  payload_fn.write format_payload(payload)
  payload_fn.close

  recipient_opts = (to + [ from ] ).map { |r| "--recipient '<#{r}>'" }.join(" ")
  sign_opts = sign ? "--sign --local-user '#{from}'" : ""
  gpg_output = run_gpg "--output - --armor --encrypt --textmode #{sign_opts} #{recipient_opts} #{payload_fn.path}"
  raise Error, (gpg_output || "gpg command failed: #{cmd}") unless $?.success?

  encrypted_payload = RMail::Message.new
  encrypted_payload.header["Content-Type"] = "application/octet-stream"
  encrypted_payload.header["Content-Disposition"] = 'inline; filename="msg.asc"'
  encrypted_payload.body = gpg_output

  control = RMail::Message.new
  control.header["Content-Type"] = "application/pgp-encrypted"
  control.header["Content-Disposition"] = "attachment"
  control.body = "Version: 1\n"

  envelope = RMail::Message.new
  envelope.header["Content-Type"] = 'multipart/encrypted; protocol="application/pgp-encrypted"'

  envelope.add_part control
  envelope.add_part encrypted_payload
  envelope
end

#have_crypto?Boolean

Returns:

  • (Boolean)


31
# File 'lib/sup/crypto.rb', line 31

def have_crypto?; !@cmd.nil? end

#sign(from, to, payload) ⇒ Object

Raises:



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/sup/crypto.rb', line 33

def sign from, to, payload
  payload_fn = Tempfile.new "redwood.payload"
  payload_fn.write format_payload(payload)
  payload_fn.close

  output = run_gpg "--output - --armor --detach-sign --textmode --local-user '#{from}' #{payload_fn.path}"

  raise Error, (output || "gpg command failed: #{cmd}") unless $?.success?

  envelope = RMail::Message.new
  envelope.header["Content-Type"] = 'multipart/signed; protocol=application/pgp-signature; micalg=pgp-sha1'

  envelope.add_part payload
  signature = RMail::Message.make_attachment output, "application/pgp-signature", nil, "signature.asc"
  envelope.add_part signature
  envelope
end

#sign_and_encrypt(from, to, payload) ⇒ Object



79
80
81
# File 'lib/sup/crypto.rb', line 79

def sign_and_encrypt from, to, payload
  encrypt from, to, payload, true
end

#verify(payload, signature) ⇒ Object

both RubyMail::Message objects



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/sup/crypto.rb', line 83

def verify payload, signature # both RubyMail::Message objects
  return unknown_status(cant_find_binary) unless @cmd

  payload_fn = Tempfile.new "redwood.payload"
  payload_fn.write format_payload(payload)
  payload_fn.close

  signature_fn = Tempfile.new "redwood.signature"
  signature_fn.write signature.decode
  signature_fn.close

  output = run_gpg "--verify #{signature_fn.path} #{payload_fn.path}"
  output_lines = output.split(/\n/)

  if output =~ /^gpg: (.* signature from .*$)/
    if $? == 0
      Chunk::CryptoNotice.new :valid, $1, output_lines
    else
      Chunk::CryptoNotice.new :invalid, $1, output_lines
    end
  else
    unknown_status output_lines
  end
end