1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "net/quic/crypto/channel_id_chromium.h" 6 7#include <string> 8 9#include "base/stl_util.h" 10#include "base/strings/string_util.h" 11#include "crypto/ec_private_key.h" 12#include "crypto/ec_signature_creator.h" 13#include "net/base/net_errors.h" 14#include "net/cert/asn1_util.h" 15#include "net/ssl/channel_id_service.h" 16 17namespace net { 18 19ChannelIDKeyChromium::ChannelIDKeyChromium( 20 crypto::ECPrivateKey* ec_private_key) 21 : ec_private_key_(ec_private_key) {} 22 23ChannelIDKeyChromium::~ChannelIDKeyChromium() {} 24 25bool ChannelIDKeyChromium::Sign(base::StringPiece signed_data, 26 std::string* out_signature) const { 27 scoped_ptr<crypto::ECSignatureCreator> sig_creator( 28 crypto::ECSignatureCreator::Create(ec_private_key_.get())); 29 if (!sig_creator) { 30 return false; 31 } 32 const size_t len1 = strlen(ChannelIDVerifier::kContextStr) + 1; 33 const size_t len2 = strlen(ChannelIDVerifier::kClientToServerStr) + 1; 34 std::vector<uint8> data(len1 + len2 + signed_data.size()); 35 memcpy(&data[0], ChannelIDVerifier::kContextStr, len1); 36 memcpy(&data[len1], ChannelIDVerifier::kClientToServerStr, len2); 37 memcpy(&data[len1 + len2], signed_data.data(), signed_data.size()); 38 std::vector<uint8> der_signature; 39 if (!sig_creator->Sign(&data[0], data.size(), &der_signature)) { 40 return false; 41 } 42 std::vector<uint8> raw_signature; 43 if (!sig_creator->DecodeSignature(der_signature, &raw_signature)) { 44 return false; 45 } 46 memcpy(WriteInto(out_signature, raw_signature.size() + 1), 47 &raw_signature[0], raw_signature.size()); 48 return true; 49} 50 51std::string ChannelIDKeyChromium::SerializeKey() const { 52 std::string out_key; 53 if (!ec_private_key_->ExportRawPublicKey(&out_key)) { 54 return std::string(); 55 } 56 return out_key; 57} 58 59// A Job handles the lookup of a single channel ID. It is owned by the 60// ChannelIDSource. If the operation can not complete synchronously, it will 61// notify the ChannelIDSource upon completion. 62class ChannelIDSourceChromium::Job { 63 public: 64 Job(ChannelIDSourceChromium* channel_id_source, 65 ChannelIDService* channel_id_service); 66 67 // Starts the channel ID lookup. If |QUIC_PENDING| is returned, then 68 // |callback| will be invoked asynchronously when the operation completes. 69 QuicAsyncStatus GetChannelIDKey(const std::string& hostname, 70 scoped_ptr<ChannelIDKey>* channel_id_key, 71 ChannelIDSourceCallback* callback); 72 73 private: 74 enum State { 75 STATE_NONE, 76 STATE_GET_CHANNEL_ID_KEY, 77 STATE_GET_CHANNEL_ID_KEY_COMPLETE, 78 }; 79 80 int DoLoop(int last_io_result); 81 void OnIOComplete(int result); 82 int DoGetChannelIDKey(int result); 83 int DoGetChannelIDKeyComplete(int result); 84 85 // Channel ID source to notify when this jobs completes. 86 ChannelIDSourceChromium* const channel_id_source_; 87 88 ChannelIDService* const channel_id_service_; 89 90 std::string channel_id_private_key_; 91 std::string channel_id_cert_; 92 ChannelIDService::RequestHandle channel_id_request_handle_; 93 94 // |hostname| specifies the hostname for which we need a channel ID. 95 std::string hostname_; 96 97 scoped_ptr<ChannelIDSourceCallback> callback_; 98 99 scoped_ptr<ChannelIDKey> channel_id_key_; 100 101 State next_state_; 102 103 DISALLOW_COPY_AND_ASSIGN(Job); 104}; 105 106ChannelIDSourceChromium::Job::Job( 107 ChannelIDSourceChromium* channel_id_source, 108 ChannelIDService* channel_id_service) 109 : channel_id_source_(channel_id_source), 110 channel_id_service_(channel_id_service), 111 next_state_(STATE_NONE) { 112} 113 114QuicAsyncStatus ChannelIDSourceChromium::Job::GetChannelIDKey( 115 const std::string& hostname, 116 scoped_ptr<ChannelIDKey>* channel_id_key, 117 ChannelIDSourceCallback* callback) { 118 DCHECK(channel_id_key); 119 DCHECK(callback); 120 121 if (STATE_NONE != next_state_) { 122 DLOG(DFATAL) << "GetChannelIDKey has begun"; 123 return QUIC_FAILURE; 124 } 125 126 channel_id_key_.reset(); 127 128 hostname_ = hostname; 129 130 next_state_ = STATE_GET_CHANNEL_ID_KEY; 131 switch (DoLoop(OK)) { 132 case OK: 133 channel_id_key->reset(channel_id_key_.release()); 134 return QUIC_SUCCESS; 135 case ERR_IO_PENDING: 136 callback_.reset(callback); 137 return QUIC_PENDING; 138 default: 139 channel_id_key->reset(); 140 return QUIC_FAILURE; 141 } 142} 143 144int ChannelIDSourceChromium::Job::DoLoop(int last_result) { 145 int rv = last_result; 146 do { 147 State state = next_state_; 148 next_state_ = STATE_NONE; 149 switch (state) { 150 case STATE_GET_CHANNEL_ID_KEY: 151 DCHECK(rv == OK); 152 rv = DoGetChannelIDKey(rv); 153 break; 154 case STATE_GET_CHANNEL_ID_KEY_COMPLETE: 155 rv = DoGetChannelIDKeyComplete(rv); 156 break; 157 case STATE_NONE: 158 default: 159 rv = ERR_UNEXPECTED; 160 LOG(DFATAL) << "unexpected state " << state; 161 break; 162 } 163 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); 164 return rv; 165} 166 167void ChannelIDSourceChromium::Job::OnIOComplete(int result) { 168 int rv = DoLoop(result); 169 if (rv != ERR_IO_PENDING) { 170 scoped_ptr<ChannelIDSourceCallback> callback(callback_.release()); 171 callback->Run(&channel_id_key_); 172 // Will delete |this|. 173 channel_id_source_->OnJobComplete(this); 174 } 175} 176 177int ChannelIDSourceChromium::Job::DoGetChannelIDKey(int result) { 178 next_state_ = STATE_GET_CHANNEL_ID_KEY_COMPLETE; 179 180 return channel_id_service_->GetOrCreateChannelID( 181 hostname_, 182 &channel_id_private_key_, 183 &channel_id_cert_, 184 base::Bind(&ChannelIDSourceChromium::Job::OnIOComplete, 185 base::Unretained(this)), 186 &channel_id_request_handle_); 187} 188 189int ChannelIDSourceChromium::Job::DoGetChannelIDKeyComplete(int result) { 190 DCHECK_EQ(STATE_NONE, next_state_); 191 if (result != OK) { 192 DLOG(WARNING) << "Failed to look up channel ID: " << ErrorToString(result); 193 return result; 194 } 195 196 std::vector<uint8> encrypted_private_key_info( 197 channel_id_private_key_.size()); 198 memcpy(&encrypted_private_key_info[0], channel_id_private_key_.data(), 199 channel_id_private_key_.size()); 200 201 base::StringPiece spki_piece; 202 if (!asn1::ExtractSPKIFromDERCert(channel_id_cert_, &spki_piece)) { 203 return ERR_UNEXPECTED; 204 } 205 std::vector<uint8> subject_public_key_info(spki_piece.size()); 206 memcpy(&subject_public_key_info[0], spki_piece.data(), spki_piece.size()); 207 208 crypto::ECPrivateKey* ec_private_key = 209 crypto::ECPrivateKey::CreateFromEncryptedPrivateKeyInfo( 210 ChannelIDService::kEPKIPassword, encrypted_private_key_info, 211 subject_public_key_info); 212 if (!ec_private_key) { 213 // TODO(wtc): use the new error code ERR_CHANNEL_ID_IMPORT_FAILED to be 214 // added in https://codereview.chromium.org/338093012/. 215 return ERR_UNEXPECTED; 216 } 217 channel_id_key_.reset(new ChannelIDKeyChromium(ec_private_key)); 218 219 return result; 220} 221 222ChannelIDSourceChromium::ChannelIDSourceChromium( 223 ChannelIDService* channel_id_service) 224 : channel_id_service_(channel_id_service) { 225} 226 227ChannelIDSourceChromium::~ChannelIDSourceChromium() { 228 STLDeleteElements(&active_jobs_); 229} 230 231QuicAsyncStatus ChannelIDSourceChromium::GetChannelIDKey( 232 const std::string& hostname, 233 scoped_ptr<ChannelIDKey>* channel_id_key, 234 ChannelIDSourceCallback* callback) { 235 scoped_ptr<Job> job(new Job(this, channel_id_service_)); 236 QuicAsyncStatus status = job->GetChannelIDKey(hostname, channel_id_key, 237 callback); 238 if (status == QUIC_PENDING) { 239 active_jobs_.insert(job.release()); 240 } 241 return status; 242} 243 244void ChannelIDSourceChromium::OnJobComplete(Job* job) { 245 active_jobs_.erase(job); 246 delete job; 247} 248 249} // namespace net 250