OpenSSH 授权密钥文件的最佳 perl 正则表达式?

Posted

技术标签:

【中文标题】OpenSSH 授权密钥文件的最佳 perl 正则表达式?【英文标题】:Best perl regex for OpenSSH authorized_keys file? 【发布时间】:2018-06-25 17:06:16 【问题描述】:

我用 perl 编写了一个小程序,它解析授权文件,其中包含由 OpenSSH 的 ssh-keygen 创建的公钥。

文件中的每个有效行都包含一个公钥。 将跳过空行和以“#”开头的行。 行可能非常长,最大可达 8kB。

公钥由以下以空格分隔的字段组成,按顺序排列:

options [可选] 任意数量的逗号分隔的不区分大小写的选项规范。不允许有空格,双引号内除外.. keytype [必需] keytype 是可以包含短划线或下划线的字母数字字符串。 base64 编码的密钥 [必需] 一个不包含空格的字符串 comment [可选] 可能包含空格的字符串

注意,我在实际程序中验证了正则表达式之外的 keytype 和 base64 编码的密钥,以便将来在 OpenSSH 添加或删除密钥算法时进行维护。正则表达式只是解析行。

【问题讨论】:

字段是否必须按显示的顺序排列?正如问题中描述的那样,如何区分单个选项和键类型?编码的密钥中是否必须包含= @zdim,是的,这是必需的顺序。密钥类型和选项取决于您运行的 OpenSSH 版本;例如,旧版本可能不支持 ed25519 键类型或 restrict 关键字。 Base64 使用= 符号作为填充,因此它只能出现在编码密钥的末尾,并不总是出现。 好的。然后我看不出程序如何判断第一个单词是单个选项(无逗号)还是键类型,或者在编码键之前有多少键类型。就你所说的,他们没有什么不同;它们可能都只是字母数字字符串。 @zdim,每行只能有一个键类型。但是,是的,您已经找到了难题;使第一个字段可选是一个非常奇怪的设计选择。 (考虑到 OpenSSH 在计算行业的重要性,我对不赞成票感到有点惊讶。我认为这是一个有趣的问题!) 我猜不赞成票(我没有这样做)是因为问题只是提出问题,没有显示代码,所以它与网站的文字和精神无关。您作为答案发布的代码是另一回事,而如果您将其包含在问题中,它将更适合代码审查。这是一个难题:这是一个重要的话题,但您确实有工作代码。 【参考方案1】:

这是我想出的,它适用于我的测试数据。

  if (my ($koptions, $ktype, $kbase64, $kcomment) =$_ !~

       /^(?:((?:[!-~]|\s(?=.*"))+)\s+)? # optional key options
         ([a-z0-9_-]+)\s+               # key type followed by a space
         ([\.=a-z0-9\/+_-]+)            # RFC4253 base64 encoded key
         (?:\s+(.*))?$                  # optional comment
       /xxia)              # ASCII, Case Insensitive, and exploded

    $koptions //= "NONE";
    $kcomment //= "NONE";
    print "\nkoptions are $koptions\n";
    print "ktype is $ktype\n";
    print "kbase64 is $kbase64\n";
    print "kcomment is $kcomment\n";
   else 
    print "Incomprehensible line in ~/.ssh/authorized_keys!\n"
  

示例文件:

ssh-rsa AAAAB3Nza...LiPk== user@example.net
from="*.sales.example.net,!pc.sales.example.net" ssh-rsa AAAAB2...19Q== john@example.net
ssh-ed25519 AAA3Nzsdfsfsd...fdsXhsdfsfWqfw this is a comment
command="dump /home",no-pty,no-port-forwarding ssh-dss AAAAC3...51R== example.net
permitopen="192.0.2.1:80",permitopen="192.0.2.2:25" ssh-dss AAAAB5...21S==
tunnel="0",command="sh /etc/netstart tun0" ssh-rsa AAAA...== jane@example.net
restrict,command="uptime" ecdsa-sha2-nistp521 AAAA1C8...32Tv==
restrict,pty,command="nethack" ssh-rsa AAAA1f8...IrrC5== user@example.net

对于这种风格或缺乏风格,我深表歉意。

有没有人有更优雅的?

【讨论】:

谢谢,在您提到之前,我对 stackexchange codereview 并不熟悉!我在这里这样做是为了让任何搜索此类正则表达式的人都可以轻松找到它。【参考方案2】:

OpenSSH 的 docs 说

authorized_keys 的格式在 sshd(8) 手册页中有描述。

sshd(8) manual page 说

AuthorizedKeysFile 指定包含公钥的文件 […] 每个 文件的行包含一个键(空行和以 '#' 作为 cmets 被忽略)。 公钥由以下内容组成 空格分隔的字段:选项、键类型、base64 编码键、 评论。 选项字段是可选的。 密钥类型为“ecdsa-sha2-nistp256”、“ecdsa-sha2-nistp384”、“ecdsa-sha2-nistp521”、“ssh-ed25519”、“ssh-dss”或“ssh-rsa”;强> 评论字段不用于任何事情(但可能方便 用户识别密钥)。 […] 选项(如果存在)包括 逗号分隔的选项规范。不允许有空格,除了 双引号内。

如果我依赖此文档并采用可能的 keytypes 文字,那么我 得到以下解决方案:

#!/usr/bin/env perl

use strict;
use warnings;

my $possible_types = join('|', qw(ecdsa-sha2-nistp256 
                                  ecdsa-sha2-nistp384 
                                  ecdsa-sha2-nistp521 
                                  ssh-ed25519 
                                  ssh-dss 
                                  ssh-rsa));

my $pattern = qr/^(?:(.*)\s+)?                 # optional options
                  ($possible_types) \s+ (\S+)  # mandatory type and key
                  (?:\s+(.*))?$/x;             # optional comment

while( <DATA> ) 
    if (/$pattern/) 
        my ($options, $type, $key, $comment) = ($1 // 'NONE', $2, $3, $4);
        print "options: '$options'\n";
        print "type:    '$type'\n";
        print "key:     '$key'\n";
        print "comment: '$comment'\n";
     else 
        print "unrecognized line: $_";
    
    print '-' x 30, "\n";


__DATA__
from="*.sales.example.net,!pc.sales.example.net" ssh-rsa AAAAB2...19Q== john@example.net
ssh-ed25519 AAA3Nzsdfsfsd...fdsXhsdfsfWqfw this is a comment
command="dump /home",no-pty,no-port-forwarding ssh-dss AAAAC3...51R== example.net
permitopen="192.0.2.1:80",permitopen="192.0.2.2:25" ssh-dss AAAAB5...21S==
tunnel="0",command="sh /etc/netstart tun0" ssh-rsa AAAA...== jane@example.net
restrict,command="uptime" ecdsa-sha2-nistp521 AAAA1C8...32Tv==
restrict,pty,command="nethack" ssh-rsa AAAA1f8...IrrC5== user@example.net

之所以有效,是因为它认为六种可能的键类型是理所当然的,然后搜索周围: 选项(如果有)必须是键类型之前的字符串;键类型后跟键(始终),并且可以选择添加注释。

不过,我不喜欢这种方法,因为它具有硬编码的可能键类型。对于您的输入,它会打印:

options: 'from="*.sales.example.net,!pc.sales.example.net"'
type:    'ssh-rsa'
key:     'AAAAB2...19Q=='
comment: 'john@example.net'
------------------------------
options: 'NONE'
type:    'ssh-ed25519'
key:     'AAA3Nzsdfsfsd...fdsXhsdfsfWqfw'
comment: 'this is a comment'
------------------------------
options: 'command="dump /home",no-pty,no-port-forwarding'
type:    'ssh-dss'
key:     'AAAAC3...51R=='
comment: 'example.net'
------------------------------
options: 'permitopen="192.0.2.1:80",permitopen="192.0.2.2:25"'
type:    'ssh-dss'
key:     'AAAAB5...21S=='
comment: ''
------------------------------
options: 'tunnel="0",command="sh /etc/netstart tun0"'
type:    'ssh-rsa'
key:     'AAAA...=='
comment: 'jane@example.net'
------------------------------
options: 'restrict,command="uptime"'
type:    'ecdsa-sha2-nistp521'
key:     'AAAA1C8...32Tv=='
comment: ''
------------------------------
options: 'restrict,pty,command="nethack"'
type:    'ssh-rsa'
key:     'AAAA1f8...IrrC5=='
comment: 'user@example.net'
------------------------------

【讨论】:

手册页大部分是正确的,尽管它是特定于版本的。它确实没有提到评论中可以有未加引号的空格,或者完全丢失,但是您的代码可以处理得很好。我喜欢您通过加入数组来构建正则表达式的方式,这是一个非常好的可维护习惯用法。当允许的加密类型在未来不可避免地发生变化时,初级程序员(或匆忙的 unix 灰胡子)会发现这很容易更新。

以上是关于OpenSSH 授权密钥文件的最佳 perl 正则表达式?的主要内容,如果未能解决你的问题,请参考以下文章

sh ssh openssh授权密钥

Linux openssh openssl

在 Perl 的 ssh 连接中捕获错误主机名的错误消息(使用 Net::OpenSSH)

想要使用 c++ 或 perl 使用 OpenSSH 和 SFTP 通过 Internet 发送文件---Windows Vista 两者

Perl:使用 Net::OpenSSH 的 scp_put 拒绝权限

openssh远程登录服务器端和基于密钥的认证机制