Module: RStyx::Keyring
- Defined in:
- lib/rstyx/keyring.rb
Defined Under Namespace
Classes: Authinfo, Certificate, FileWrapper, InfPrivateKey, InfPublicKey
Constant Summary collapse
- MAX_MSG =
4096
Class Method Summary collapse
-
.auth(fd, role, info, algs) ⇒ Object
Perform mutual authentication over a network connection fd.
-
.basicauth(fd, info) ⇒ Object
Perform the Inferno authentication protocol, reading messages from and writing messages to fd, given the authentication information object info.
-
.big2s(val) ⇒ Object
Convert a bignum val into a big-endian base64-encoded byte string.
-
.bit_size(i) ⇒ Object
Determine the size of i in bits.
-
.getmsg(fd) ⇒ Object
Get a message from fd.
-
.mod_exp(b, e, m) ⇒ Object
Modular exponentiation.
-
.randpq(p, q) ⇒ Object
Generate a random number between p and q.
-
.rsadecrypt(sk, data) ⇒ Object
Perform RSA decryption given a private key sk and ciphertext data.
-
.rsaencrypt(pk, data) ⇒ Object
Perform RSA encryption given a (public or private) key pk and plaintext data represented as a BigInteger.
-
.rsaverify(m, sig, key) ⇒ Object
Verify an RSA signature, given the hash m, the signature data sig, and the signer public key key.
-
.s2big(str2) ⇒ Object
Convert a big-endian, base64-encoded byte string str2 into a bignum.
-
.senderrmsg(fd, data) ⇒ Object
Send an error message to fd, prefixed by a ! and a three digit size.
-
.sendmsg(fd, data) ⇒ Object
Send a message to fd, prefixed by a four-digit zero-padded size.
-
.setlinecrypt(fd, role, algs) ⇒ Object
Set cryptographic algorithms in use, given a connection object fd a role role (which may be :client or :server), and a list of algorithms.
-
.sign(sk, exp, a) ⇒ Object
Sign a certificate, given a private key sk, an expiration time exp in seconds from the Epoch, and the data to sign a.
-
.str2big(str) ⇒ Object
Convert a big-endian byte string str into a bignum.
-
.verify(pk, c, a) ⇒ Object
Verify a certificate c given the public key of the signer pk, and the actual data of the certificate a.
Class Method Details
.auth(fd, role, info, algs) ⇒ Object
Perform mutual authentication over a network connection fd. The role is the role of the connection, which may be either of the symbols :client or :server, info holds an Authinfo object containing this peer’s authentication information, and algs the bulk encryption algorithms supported by this peer. See Inferno’s keyring-auth(2) for more details on how this should work.
600 601 602 603 604 |
# File 'lib/rstyx/keyring.rb', line 600 def self.auth(fd, role, info, algs) res = basicauth(fd, info) setlinecrypt(fd, role, algs) return(res) end |
.basicauth(fd, info) ⇒ Object
Perform the Inferno authentication protocol, reading messages from and writing messages to fd, given the authentication information object info.
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 |
# File 'lib/rstyx/keyring.rb', line 446 def self.basicauth(fd, info) secret = peerauth = nil begin # 1. Version negotiation sendmsg(fd, "1") buf = Keyring.getmsg(fd) vers = buf.to_i # 2. Check version if vers != 1 || buf.length > 4 raise LocalAuthErr.new("incompatible authentication protocol") end if info.nil? raise LocalAuthErr.new("no authentication information") end if info.p.nil? raise LocalAuthErr.new("missing diffie hellman mod") end if info.alpha.nil? raise LocalAuthErr.new("missing diffie hellman base") end if info.mysk.nil? || info.mypk.nil? || info.cert.nil? || info.spk.nil? raise LocalAuthErr.new("invalid authentication information") end if info.p <= 0 raise LocalAuthErr.new("negative modulus") end # 3. Diffie-Hellman authentication protocol. low = info.p >> (Keyring.bit_size(info.p) / 4) r0 = Keyring.randpq(low, info.p) alphar0 = mod_exp(info.alpha, r0, info.p) sendmsg(fd, Keyring.big2s(alphar0)) sendmsg(fd, info.cert.to_s) sendmsg(fd, info.mypk.to_s) # 4. Receive peer's alpha**r1 mod p and the peer's certificate # and public key. alphar1 = Keyring.s2big(Keyring.getmsg(fd)) if info.p <= alphar1 raise LocalAuthErr.new("implausible parameter value") end if alphar0 == alphar1 raise LocalAuthErr.new("possible replay attack") end # 5. Verify the authenticity of the peer's certificate. hiscert = Certificate.from_s(getmsg(fd)) hispkbuf = getmsg(fd) hispk = InfPublicKey.from_s(hispkbuf) unless verify(info.spk, hiscert, hispkbuf) raise LocalAuthErr.new("pk doesn't match certificate") end if hiscert.exp != 0 && (Time.at(hiscert.exp) <= Time.now) raise LocalAuthErr.new("certificate expired") end # 6. Send a certificate to the peer with alpha**r0 mod p and # alpha**r1 mod p. alphabuf = Keyring.big2s(alphar0) + Keyring.big2s(alphar1) alphacert = sign(info.mysk, 0, alphabuf) sendmsg(fd, alphacert.to_s) # 7. Receive the peer's certificate alphacert = Certificate.from_s(getmsg(fd)) alphabuf = Keyring.big2s(alphar1) + Keyring.big2s(alphar0) # 8. Verify the certificate from the peer unless verify(hispk, alphacert, alphabuf) raise LocalAuthErr.new("signature did not match pk") end # alpha0r1 is the shared secret alpha0r1 = mod_exp(alphar1, r0, info.p) secret = "" val = alpha0r1 while val > 0 c = val % 256 secret << c.chr val = val / 256 end # Remove any leading nulls secret =~ /\0*(.*)/ secret = $1 peerauth = Authinfo.new(nil, hispk, hiscert, info.spk, info.alpha, info.p) # 9. Send a protocol message containing OK back to the client. sendmsg(fd, "OK") rescue IOError => e raise LocalAuthErr.new("I/O error: #{e.}") rescue InvalidCertificateException => e senderrmsg(fd, "remote: #{e.}") raise e rescue InvalidKeyException => e senderrmsg(fd, "remote: #{e.}") raise e rescue NoSuchAlgorithmException => e senderrmsg(fd, "remote: unsupported algorithm: #{e.}") raise e rescue LocalAuthErr => e senderrmsg(fd, "remote: #{e.}") raise e rescue RemoteAuthErr => e senderrmsg(fd, "missing your authentication data") raise AuthenticationException.new(e.) end begin # 10. Receive an OK from the peer. until /OK/ =~ getmsg(fd) end rescue Exception => e raise AuthenticationException.new("i/o error: #{e.}") end return([peerauth, secret]) end |
.big2s(val) ⇒ Object
Convert a bignum val into a big-endian base64-encoded byte string
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/rstyx/keyring.rb', line 57 def self.big2s(val) str = "" while val > 0 c = val % 256 str = c.chr + str val = val / 256 end # Force leading 0 byte for compatibility with older representation # See libinterp/keyring.c function bigtobase64. if str.length != 0 && ((str[0] & 0x80) != 0) str = "\0" + str end str = [str].pack("m") # Ruby will add newlines into the Base64 representation. Remove # them; they're useless. str.tr!("\n", "") return(str) end |
.bit_size(i) ⇒ Object
Determine the size of i in bits.
95 96 97 98 99 100 101 102 103 |
# File 'lib/rstyx/keyring.rb', line 95 def self.bit_size(i) hibit = i.size * 8 - 1 while (i[hibit] == 0) hibit -= 1 break if hibit < 0 end return(hibit + 1) end |
.getmsg(fd) ⇒ Object
Get a message from fd. A message is defined as a four-digit number (representing the size), followed by a newline, followed by a number of bytes equal to the number. The number may be preceded by an exclamation point, in which the data becomes the message sent as a RemoteAuthErr exception’s error text.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/rstyx/keyring.rb', line 141 def self.getmsg(fd) num = fd.read(5) if num[4..4] != "\n" raise IOError.new("bad message syntax") end iserr = false i = nil n = 0 if num[0..0] == '!' iserr = true n = num[1,3].to_i else n = num.to_i end if n < 0 || n > MAX_MSG raise IOError.new("message syntax") end z = fd.read(n) if iserr raise RemoteAuthErr.new(z) end return(z) end |
.mod_exp(b, e, m) ⇒ Object
Modular exponentiation. Computes b^e mod m using the square and multiply method.
80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/rstyx/keyring.rb', line 80 def self.mod_exp(b, e, m) res = 1 while e > 0 if e[0] == 1 res = (res * b) % m end e >>= 1 b = (b*b) % m end return(res) end |
.randpq(p, q) ⇒ Object
Generate a random number between p and q. Uses OpenSSL::Random to generate the random number.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/rstyx/keyring.rb', line 109 def self.randpq(p, q) if p > q t = p p = q q = t end diff = q - p if diff < 2 raise RuntimeError.new("range must be at least two") end l = Keyring.bit_size(diff) t = 1 << l l = (l + 7) & ~7 # nearest byte slop = t % diff r = -1 while r < slop buf = OpenSSL::Random.random_bytes(l) r = 0 buf.each_byte do |b| r = r*256 + b end end return((r % diff) + p) end |
.rsadecrypt(sk, data) ⇒ Object
Perform RSA decryption given a private key sk and ciphertext data.
619 620 621 622 623 624 625 626 627 |
# File 'lib/rstyx/keyring.rb', line 619 def self.rsadecrypt(sk, data) p = sk.p.to_i v1 = data % p q = sk.q.to_i v2 = data % q v1 = mod_exp(v1, sk.dmp1.to_i, p) v2 = mod_exp(v2, sk.dmq1.to_i, q) return((((v2 - v1)*sk.iqmp) % q)*p + v1) end |
.rsaencrypt(pk, data) ⇒ Object
Perform RSA encryption given a (public or private) key pk and plaintext data represented as a BigInteger. Returns the RSA ciphertext as a BigInteger
611 612 613 |
# File 'lib/rstyx/keyring.rb', line 611 def self.rsaencrypt(pk, data) return(mod_exp(data, pk.e.to_i, pk.n.to_i)) end |
.rsaverify(m, sig, key) ⇒ Object
Verify an RSA signature, given the hash m, the signature data sig, and the signer public key key.
633 634 635 |
# File 'lib/rstyx/keyring.rb', line 633 def self.rsaverify(m, sig, key) return(rsaencrypt(key, sig) == m) end |
.s2big(str2) ⇒ Object
Convert a big-endian, base64-encoded byte string str2 into a bignum
49 50 51 52 |
# File 'lib/rstyx/keyring.rb', line 49 def self.s2big(str2) str = str2.unpack("m")[0] return(str2big(str)) end |
.senderrmsg(fd, data) ⇒ Object
Send an error message to fd, prefixed by a ! and a three digit size.
179 180 181 182 |
# File 'lib/rstyx/keyring.rb', line 179 def self.senderrmsg(fd, data) fd.printf("!%03d\n", data.length) fd.write(data) end |
.sendmsg(fd, data) ⇒ Object
Send a message to fd, prefixed by a four-digit zero-padded size.
170 171 172 173 |
# File 'lib/rstyx/keyring.rb', line 170 def self.sendmsg(fd, data) fd.printf("%04d\n", data.length) fd.write(data) end |
.setlinecrypt(fd, role, algs) ⇒ Object
Set cryptographic algorithms in use, given a connection object fd a role role (which may be :client or :server), and a list of algorithms. Only the first algorithm listed is in use. This is a stub until we can figure out exactly how Inferno does cryptographic protocol negotiation.
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 |
# File 'lib/rstyx/keyring.rb', line 572 def self.setlinecrypt(fd, role, algs) if role == :client if (!algs.nil? && algs.length > 0) alg = algs[0] else alg = "none" # we need to either figure out how to use SSL without its handshake or write our own code to do cryptography manually. end sendmsg(fd, alg) elsif role == :server alg = self.getmsg(fd) if alg != "none" raise IOError.new("unsupported algorithm: " + alg) end else raise IOException.new("invalid role #{role.to_s}") end return(alg) end |
.sign(sk, exp, a) ⇒ Object
Sign a certificate, given a private key sk, an expiration time exp in seconds from the Epoch, and the data to sign a.
658 659 660 661 662 663 664 665 |
# File 'lib/rstyx/keyring.rb', line 658 def self.sign(sk, exp, a) sha1 = Digest::SHA1.new sha1.update(a) sha1.update("#{sk.owner} #{exp}") digest = str2big(sha1.digest) sig = rsadecrypt(sk.sk, digest) return(Certificate.new("rsa", "sha1", sk.owner, exp, sig)) end |
.str2big(str) ⇒ Object
Convert a big-endian byte string str into a bignum.
38 39 40 41 42 43 44 |
# File 'lib/rstyx/keyring.rb', line 38 def self.str2big(str) val = 0 str.each_byte do |b| val = val*256 + b end return(val) end |
.verify(pk, c, a) ⇒ Object
Verify a certificate c given the public key of the signer pk, and the actual data of the certificate a.
641 642 643 644 645 646 647 648 649 650 651 652 |
# File 'lib/rstyx/keyring.rb', line 641 def self.verify(pk, c, a) # Check if the certificate algorithm is supported. At the moment # only RSA signatures over SHA-1 are supported. unless c.sa == "rsa" && (c.ha == "sha1" || c.ha == "sha") return(false) end sha1 = Digest::SHA1.new sha1.update(a) sha1.update("#{c.signer} #{c.exp}") val = str2big(sha1.digest) return(rsaverify(val, c.rsa, pk.pk)) end |