File Coverage

blib/lib/Net/BEEP/Lite/TLSProfile.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             # $Id: TLSProfile.pm,v 1.1 2003/09/11 23:25:51 davidb Exp $
2             #
3             # Copyright (C) 2003 Verisign, Inc.
4             #
5             # This library is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU Lesser General Public
7             # License as published by the Free Software Foundation; either
8             # version 2.1 of the License, or (at your option) any later version.
9             #
10             # This library is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13             # Lesser General Public License for more details.
14             #
15             # You should have received a copy of the GNU Lesser General Public
16             # License along with this library; if not, write to the Free Software
17             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18             # USA
19              
20             package Net::BEEP::Lite::TLSProfile;
21              
22             =head1 NAME
23              
24             Net::BEEP::Lite::TLSProfile - A TLS tuning profile for Net::BEEP::Lite.
25              
26             =head1 SYNOPSIS
27              
28             use Net::BEEP::Lite;
29             use Net::BEEP::Lite::TLSProfile;
30              
31             my $c_session = Net::BEEP::Lite::beep_connect(Host => localhost,
32             Port => 12345) ||
33             die "could not connect to beep peer: $!";
34              
35             if ($c_session->has_remote_profile($Net::BEEP::Lite::TLSProfile::URI)) {
36             my $tls_profile = new Net::BEEP::Lite::TLSProfile(SSL_verify_mode => 0x01);
37              
38             $tls_profile->start_TLS($c_session) || die "could not establish TLS";
39              
40             print "peer certificate info: ", $session->{peer_certificate}, "\n";
41             }
42              
43             ---
44              
45             use Net::BEEP::Lite;
46             use Net::BEEP::Lite::TLSProfile;
47              
48             my $other_profile = ...;
49              
50             my $tls_profile = Net::BEEP::Lite::TLSProfile
51             (Server => 1,
52             Callback => sub { my $session = shift;
53             $session->add_local_profile($other_profile); },
54             SSL_cert_file => 'my_cert.pem',
55             SSL_key_file => 'my_key.pem',
56             SSL_ca_file => 'my_ca.pem',
57             SSL_passwd_db => sub { "some-passwd" });
58              
59             Net::BEEP::Lite::beep_listen(Port => 12345,
60             Method => 'fork',
61             Profiles => [ $tls_profile ]);
62              
63             =head1 ABSTRACT
64              
65             is a TLS profile for BEEP as defined by
66             RFC 3080 for use with the C module.
67              
68             =head1 DESCRIPTION
69              
70             This is a TLS profile for BEEP as defined by RFC 3080 for use with the
71             C module. It can be use for both the initiator and
72             listener roles. This module relies heavily on the C
73             module for the TLS implementation.
74              
75             =cut
76              
77 1     1   32708 use Carp;
  1         3  
  1         86  
78 1     1   5 use strict;
  1         1  
  1         28  
79 1     1   4 use warnings;
  1         5  
  1         23  
80              
81 1     1   853 use XML::LibXML;
  0            
  0            
82             use IO::Socket::SSL;
83              
84             use Net::BEEP::Lite::Message;
85              
86             use base qw(Net::BEEP::Lite::BaseProfile);
87              
88             our($URI, $errstr, $VERSION);
89              
90             $URI = 'http://iana.org/beep/TLS';
91              
92             $VERSION = '0.01';
93              
94             =head1 CONSTRUCTOR
95              
96             =over 4
97              
98             =item new( I )
99              
100             This is the main constructor. It takes a named parameter lists as its
101             argument. See the C method of a list of valid parameters.
102             It also takes the named parameters of C.
103              
104             =back
105              
106             =cut
107              
108             sub new {
109             my $this = shift;
110             my $class = ref($this) || $this;
111              
112             my $self = {};
113              
114             bless $self, $class;
115              
116             $self->SUPER::initialize(@_);
117             $self->initialize(@_);
118              
119             $self->{parser} = XML::LibXML->new();
120              
121             $self;
122             }
123              
124             =head1 METHODS
125              
126             =over 4
127              
128             =item initialize( I )
129              
130             Initialze this profile. This is generally called by the constructor.
131             It takes the following named parameters:
132              
133             =over 4
134              
135             =item Server
136              
137             Set this to true when this profile is being used by a BEEP peer in the
138             Listener role. This will tell the underlying TLS negotation that it
139             is the server. If this isn't set correctly, the TLS negotiation will
140             fail.
141              
142             =item Callback
143              
144             If this is set to a sub reference, this subroutine will be called upon
145             a successful TLS negotiation. It will be passed a reference to the
146             session as its first and only argument. For example, this might be
147             used to change the local profiles offered.
148              
149             =item SSL_*
150              
151             These are parameters that are understood by C.
152             You will probably want to use a few of them: SSL_cert_file,
153             SSL_key_file, and SSL_verify_mode are typical.
154              
155             =back
156              
157             =cut
158              
159             sub initialize {
160             my $self = shift;
161             my %args = @_;
162              
163             $self->{uri} = $URI;
164             $self->{_callback} = 0;
165             $self->{_ssl_args} = { SSL_version => 'TLSv1' };
166              
167             for (keys %args) {
168             my $val = $args{$_};
169              
170             /^server$/io and do {
171             $self->{_is_server} = $val;
172             next;
173             };
174             /^callback$/io and do {
175             $self->{_callback} = $val;
176             next;
177             };
178             /^SSL_/ and do {
179             $self->{_ssl_args}->{$_} = $val;
180             next;
181             };
182             }
183             }
184              
185              
186             # This handles the piggybacked request. IMO, this is really
187             # the only way to do TLS. I'm not sure why anyone would bother with
188             # the non-piggybacked form of this profile.
189             #
190             # NOTE: this handles the back end of the exchange itself, so we can
191             # drop right into the TLS negotation after sending the
192             # response.
193             sub start_channel_request {
194             my $self = shift;
195             my $session = shift;
196             my $message = shift;
197             my $data = shift;
198              
199             my $el = $self->_parse_content($data);
200             if ($el->nodeName eq 'ready') {
201              
202             # FIXME: deal with version attribute.
203              
204             # send response ourselves.
205             my $proceed_cdata = new XML::LibXML::CDATASection("");
206             $session->{mgmt_profile}->send_profile_message
207             ($session, $message->msgno(), $self->uri(), $proceed_cdata, 0);
208              
209             # start TLS
210             $self->_start_TLS($session);
211              
212             # inform the management profile to do nothing else.
213             return 'NUL';
214             }
215             else {
216             # we create the channel, but return an embedded error.
217             return ('RPY', "unknown request.", 0);
218             }
219             }
220              
221             # This handles server side of the non-piggybacked form of the TLS
222             # negotiation.
223             sub MSG {
224             my $self = shift;
225             my $session = shift;
226             my $message = shift;
227              
228             if ($message->content_type() ne 'application/beep+xml') {
229             my $resp = new Net::BEEP::Lite::Message
230             (Type => 'ERR',
231             Msgno => $message->msgno(),
232             Channel => $message->channel_number(),
233             ContentType => 'application/beep+xml',
234             Content => "Unknown request.");
235              
236             $session->send_message($resp);
237             return;
238             }
239              
240             my $el = $self->_parse_content($message->content());
241             if ($el->nodeName eq 'ready') {
242              
243             # send response
244             my $resp = new Net::BEEP::Lite::Message
245             (Type => 'RPY',
246             Channel => $message->channel_number(),
247             Msgno => $message->msgno(),
248             ContentType => 'application/beep+xml',
249             Content => '');
250              
251             $session->send_message($resp);
252              
253             # start TLS
254             $self->_start_TLS($session);
255             }
256             else {
257              
258             my $resp = new Net::BEEP::Lite::Message
259             (Type => 'ERR',
260             Channel => $message->channel_number(),
261             Msgno => $message->msgno(),
262             ContentType => 'application/beep+xml',
263             Content => "Unknown request.");
264              
265             $session->send_message($resp);
266             }
267              
268             $message;
269             }
270              
271              
272             # This handles the client side of the non-piggybacked form of this
273             # profile.
274             sub RPY {
275             my $self = shift;
276             my $session = shift;
277             my $message = shift;
278              
279             my $el = $self->_parse_content($message->content());
280             if ($el->nodeName eq 'proceed') {
281              
282             # start TLS
283             $self->_start_TLS($session) || return undef;
284             }
285             else {
286             $errstr = "Non-proceed response: " . $message->content();
287             return undef;
288             }
289              
290             $message;
291             }
292              
293             # This handles error messages on the channel. Presumably, a
294             # non-piggybacked "ready" request was broken in some way.
295             sub ERR {
296             my $self = shift;
297             my $session = shift;
298             my $message = shift;
299              
300             $errstr = "error response: ", $message->content();
301              
302             $message;
303             }
304              
305             sub _parse_content {
306             my $self = shift;
307             my $content = shift;
308              
309             my $doc = $self->{parser}->parse_string($content);
310             $doc->documentElement();
311             }
312              
313             # This method actually does the TLS negotiation. It returns undef if
314             # it fails, and true if it succeeds, and does a tuning reset
315             # regardless. This should only be called once the peer is past the
316             # "" phase (either it sent it or received it).
317             sub _start_TLS {
318             my $self = shift;
319             my $session = shift;
320             my $res;
321              
322             # start SSL
323             my $sock = $session->_socket();
324             my %ssl_args = %{$self->{_ssl_args}};
325             $ssl_args{SSL_server} = $self->{_is_server} if $self->{_is_server};
326              
327              
328             my $ssl_sock = IO::Socket::SSL->start_SSL($sock, %ssl_args);
329              
330             if ($ssl_sock) {
331             # SSL negotation succeeded.
332             $session->_set_socket($ssl_sock);
333              
334             # if there is a peer cert, load its info into the session;
335             $session->{peer_certificate} = $ssl_sock->dump_peer_certificate();
336              
337             # normally, we remove the TLS profile itself.
338             delete $session->{profiles}->{$self->uri()};
339              
340             # if there is a callback, call it.
341             &{$self->{_callback}}($session) if $self->{_callback};
342              
343             # FIXME: normally this would be done below, but some testing has
344             # indicated that negotiation failure doesn't work the way it
345             # ought.
346             $session->_tuning_reset();
347              
348             $res = 1;
349             }
350             else {
351             $errstr = "SSL/TLS negotiation failed: ", &IO::Socket::SSL::errstr();
352             print STDERR $errstr if $self->{debug};
353              
354             $res = undef;
355             }
356              
357             # Do a tuning reset.
358             # NOTE: this must be done even if the TLS negotation failed.
359             # FIXME: some testing indicates otherwise, although the spec is clear.
360             #$session->_tuning_reset();
361              
362             return $res;
363             }
364              
365             =item start_TLS($session)
366              
367             This is the main routine for the client side. This will initiate a
368             request for TLS. It will return undef if it failed, setting $errstr,
369             true if it succeeded. The peer certificate info will be placed in
370             $session->{peer_certificate}.
371              
372             =cut
373              
374             sub start_TLS {
375             my $self = shift;
376             my $session = shift;
377              
378             my $mgmt_profile = $session->{mgmt_profile};
379              
380             # Start a channel for the TLS profile, piggybacking our "ready"
381             # message on the request.
382              
383             my %start_channel_args;
384             $start_channel_args{Channel} = $session->_next_channel_number();
385             $start_channel_args{URI} = $self->uri();
386             $start_channel_args{StartData} = "";
387              
388             my ($channel_num, $start_msg) = $mgmt_profile->send_start_channel_message
389             ($session, %start_channel_args);
390              
391             # look for the response to this request (RPY on channel zero with
392             # the same message number.). This will place those messages on the
393             # session's message queue. This will only matter if the TLS
394             # negotiation doesn't happen.
395              
396             # NOTE: this has to do a lot of stuff sort of manually, because the
397             # normally used routines will emit SEQs when we don't want, and will
398             # intercept channel zero messages, which we also don't want.
399              
400             my $resp;
401              
402             while (1) {
403             # get the next message, but don't emit SEQ frames!
404             $resp = $session->_recv_message(NoSEQ => 1);
405              
406             # if the message we received is the reply to our start channel
407             # request, we are done reading.
408             last if $resp->type() eq 'RPY' and $resp->channel_number() == 0 and
409             $resp->msgno() == $start_msg->msgno();
410              
411             # otherwise, we send a SEQ frame ourselves.
412             my $channel = $session->channel($resp->channel_number());
413             $session->_send_seq($channel, $channel->peer_seqno());
414              
415             # if the message we got was for channel zero (but not the one we
416             # wanted) we let the mangement profile handle it. Otherwise we
417             # queue it.
418             if ($resp->channel_number() == 0) {
419             $mgmt_profile->handle_message($session, $resp);
420             } else {
421             $session->_queue_message($resp);
422             }
423             }
424              
425             # Let the management profile do its thing.
426             $mgmt_profile->handle_message($session, $resp);
427              
428             my $root = $self->_parse_content($session->{start_channel_data});
429             if ($root->nodeName eq "proceed") {
430             return $self->_start_TLS($session);
431             }
432             else {
433             $errstr="non- response: " . $session->{start_channel_data};
434             return undef;
435             }
436             }
437              
438             =pod
439              
440             =back
441              
442             =head1 SEE ALSO
443              
444             =over 4
445              
446             =item L
447              
448             =item L
449              
450             =cut
451              
452             1;