yebin-yu / openssh_with_ukey

Other
0 stars 0 forks source link

UKEY 配置以及修改说明 #16

Open yebin-yu opened 5 months ago

yebin-yu commented 5 months ago

openssh 客户端配置

/etc/ssh/ssh_config

Host *
    SendEnv LANG LC_*
    HashKnownHosts yes
    GSSAPIAuthentication yes
PubkeyAcceptedAlgorithms sm2

/root/.ssh/config

Host *
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  IdentityFile ~/.ssh/id_sm2
  IdentitiesOnly yes
  HashKnownHosts no

建立连接

{ssh路径}ssh -p {端口号} {用户名}@{设备IP} -oHostKeyAlgorithms=sm2 -oMACs=hmac-sm3 -oKexAlgorithms=sm2-sm3 -oCiphers=sm4-ctr -vvv

注意事项

第一次插入ukey的时候,不知道ukey的公钥,不过没关系,还是用上面建立连接的命令尝试去建联,输入ukey的pin码后,会在当前目录下生成一个sm2.pub文件,里面就是base64转码后的公钥内容

yebin-yu commented 5 months ago

openssh 服务端配置 (本地测试用)

etc/ssh/sshd_config

HostKey /root/.ssh/id_sm2
PermitRootLogin yes
PubkeyAuthentication yes
AuthorizedKeysFile      /authorized_keys
X11Forwarding yes
Subsystem       sftp    /usr/lib/ssh/sftp-server
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL
MACs hmac-sm3,umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
HostKeyAlgorithms sm2,rsa-sha2-512,rsa-sha2-256,ssh-rsa,ecdsa-sha2-nistp256,ssh-ed25519
KexAlgorithms  sm2-sm3,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
Ciphers sm4-ctr,chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com
PubkeyAcceptedKeyTypes sm2
FingerprintHash sm3
PubkeyAcceptedAlgorithms sm2
PasswordAuthentication no
AllowUsers *
LOGLEVEL DEBUG3

这里配置的公钥存放文件为/authorized_keys, /authorized_keys在测试中的文件内容如下,内容为ukey的公钥

sm2 AAAAA3NtMgAAAANzbTIAAABBBKN4ft4dC8ThwwicmuITfLEjt1lOSuLdhZuJDh/5VGUhmB4sY1+3KVcoqgW8yxjECbcb9S7sImq8kVIVP4OPy84= root@yebinyu

拉起sshd进程

{sshd路径}sshd -p {端口号} -D -d -oHostKeyAlgorithms=sm2 -oMacs=hmac-sm3 -oKexAlgorithms=sm2-sm3 -oCiphers=sm4-ctr -f /etc/ssh/sshd_config_sm2

可以不加 -f /etc/ssh/sshd_config_sm2 ,默认的配置文件路径为 /etc/ssh/sshd_confg

tyxk2013 commented 4 months ago

公钥需要转换后才能放到服务器设备上

例如原始公钥
sm2 AAAAA3NtMgAAAANzbTIAAABBBKN4ft4dC8ThwwicmuITfLEjt1lOSuLdhZuJDh/5VGUhmB4sY1+3KVcoqgW8yxjECbcb9S7sImq8kVIVP4OPy84= root@yebinyu
经过转换函数(python环境下执行)
import base64
base64.b64decode("AAAAA3NtMgAAAANzbTIAAABBBKN4ft4dC8ThwwicmuITfLEjt1lOSuLdhZuJDh/5VGUhmB4sY1+3KVcoqgW8yxjECbcb9S7sImq8kVIVP4OPy84=").hex()[-130:]
得到转换后的结果,这个就是需要存到设备上的:
04a3787ede1d0bc4e1c3089c9ae2137cb123b7594e4ae2dd859b890e1ff9546521981e2c635fb7295728aa05bccb18c409b71bf52eec226abc9152153f838fcbce

服务器设备配置如下:

[~R108]dis c c ssh
#
stelnet server enable
snetconf server enable
ssh server rsa-key min-length 3072
ssh user netconf
ssh user netconf authentication-type password-sm2
ssh user netconf assign sm2-key ukeytest
ssh user netconf service-type all
ssh server-source -i Ethernet0/0/0
undo ssh server-source all-interface
undo ssh ipv6 server-source all-interface
ssh server assign sm2-host-key sm2kp
ssh server ip-block disable
ssh authorization-type default aaa
#
ssh server cipher sm4_cbc sm4_gcm sm4_ctr
ssh server hmac sm3
ssh server key-exchange sm2_kep sm2_sm3
#
ssh server publickey sm2 sm2-sm3
#               
ssh server dh-exchange min-len 3072
#               
ssh client publickey sm2 sm2-sm3
#               
ssh client cipher sm4_cbc sm4_gcm sm4_ctr
ssh client hmac sm3
ssh client key-exchange sm2_kep sm2_sm3
yebin-yu commented 4 months ago

代码分析

将ukey的公钥信息读取出来,转换成openssh支持的sshkey格式(底层其实是转换成openssl中的EVP_KEY)

openssh 在发起建联消息前,会遍历配置中给的私钥和certificate列表,如果找到了就读取信息存入 struct sshkey 中,再后续公钥测试的时候会遍历每一个私钥和certificate,使用私钥去生成公钥去测试。 所以这里首先得修改 /root/.ssh/config ,加入sm2的私钥 -- IdentityFile ~/.ssh/id_sm2

改这个配置后,如果配置文件是id_sm2,同时这个文件不存在,就会走入ukey的流程

    if ((f = fopen(filename, "r")) == NULL) {
        if (strcmp(filename, "/root/.ssh/id_sm2") == 0) {
            // 初始化kp
            *kp = sshkey_new(KEY_SM2);
            (*(*kp)).type = KEY_SM2;
            (*(*kp)).ecdsa_nid = NID_sm2;

            if (ukey_to_sshkey(*kp) != 0) {
                return 1;
            }

            return 0;
        }

        return SSH_ERR_SYSTEM_ERROR;
    }

打开ukey,保存dev,application和container信息,同时读取publickey

static int
init_ukey_container_and_dev()
{
    ULONG ulRslt = SAR_OK;

    // Get dev name and connect dev
    char szDevName[256] = {0}; 
    ULONG ulDevNameLen = 256;
    ulRslt = SKF_EnumDev(TRUE, szDevName, &ulDevNameLen);
    printf("szDevName: %s \n", szDevName);
    if (ulRslt != SAR_OK) {
        debug("SKF_EnumDev ERROR \n");
        return SSH_ERR_UKEY_ERROR;
    }

    ulRslt = SKF_ConnectDev(szDevName, &hdev);
    if (ulRslt != SAR_OK) {
        debug("SKF_ConnectDev ERROR \n");
        return SSH_ERR_UKEY_ERROR;
    }

    // Get application and connect application
    char appName[256] = {0}; 
    ULONG appnameLen = 256;
    ulRslt = SKF_EnumApplication(hdev, appName, &appnameLen);
    printf("appName: %s\n", appName);
    if (ulRslt != SAR_OK) {
        debug("SKF_EnumApplication ERROR \n");
        return SSH_ERR_UKEY_ERROR;
    }

    HANDLE happ;
    ulRslt = SKF_OpenApplication(hdev, appName, &happ);
    if (ulRslt != SAR_OK) {
        debug("SKF_OpenApplication ERROR \n");
        return SSH_ERR_UKEY_ERROR;
    }

    // Check pin
    char pinStr[32];
    ULONG retryCntMax = 3;
    ULONG currentRetryCnt = 0;
    printf("UKEY pin:");
    scanf("%s", pinStr);
    ulRslt = SKF_VerifyPIN(happ, USER_TYPE, pinStr, &retryCntMax);
    while (ulRslt != SAR_OK && currentRetryCnt++ < retryCntMax) {
        sleep(2);  // 防止暴力破解,等待两秒
        printf("UKEY pin again:");
        scanf("%s", pinStr);
        ulRslt = SKF_VerifyPIN(happ, USER_TYPE, pinStr, &retryCntMax);
    }
    if (ulRslt != SAR_OK) {
        debug("SKF_VerifyPIN ERROR \n");
        return SSH_ERR_UKEY_ERROR;
    }

    // Get container name and connect container
    char containerName[256] = {0};
    ULONG containerNameLen = 256;
    ulRslt = SKF_EnumContainer(happ, containerName, &containerNameLen);
    printf("containerName: %s\n", containerName);
    if (ulRslt != SAR_OK) {
        debug("SKF_EnumContainer ERROR \n");
        return SSH_ERR_UKEY_ERROR;
    }

    ulRslt = SKF_OpenContainer(happ, containerName, &hcontainer);
    if (ulRslt != SAR_OK) {
        debug("SKF_OpenContainer ERROR \n");
        return SSH_ERR_UKEY_ERROR;
    }

    BYTE buf[sizeof(ECCPUBLICKEYBLOB)] = {0};
    ULONG bufLen = sizeof(buf);
    ulRslt = SKF_ExportPublicKey(hcontainer, TRUE, buf, &bufLen);
    if (ulRslt != SAR_OK) {
        debug("SKF_ExportPublicKey ERROR \n");
        return SSH_ERR_UKEY_ERROR;
    }

    blob = malloc(sizeof(ECCPUBLICKEYBLOB));
    if (blob == NULL) {
        debug("malloc blob failed \n");
        return SSH_ERR_UKEY_ERROR;
    }
    memcpy(blob, buf, sizeof(ECCPUBLICKEYBLOB));
    return SAR_OK;
}

将公钥保存到sm2.pub

static int 
saveAndGetSm2Pub(ECCPUBLICKEYBLOB *blob)
{
    // blob to pubkey string
    BYTE *x = blob->XCoordinate + 32;
    BYTE *y = blob->YCoordinate + 32;

    const int headLen = 19;
    const int xLen = 32;
    const int yLen = 32;

    BYTE *head_char = malloc(headLen);
    // 00000003736d3200000003736d3200000041: always
    // 04: uncompressed
    hexToByte("00000003736d3200000003736d320000004104", 38, head_char);
    BYTE *allBytes = NULL;
    if ((allBytes = malloc(headLen + 32 + 32)) == NULL) {
        debug("allBytes malloc ERROR \n");
        return 1;
    }

    memcpy(allBytes, head_char, headLen);
    memcpy(allBytes + headLen, x, xLen);
    memcpy(allBytes + headLen + xLen, y, yLen);
    size_t pubLen = ((headLen + xLen + yLen + 2) / 3) * 4 + 1;
    char *pubStr = malloc(pubLen);
    if (b64_ntop(allBytes, headLen + xLen + yLen, pubStr, pubLen) == -1) {
        debug("b64_ntop ERcROR \n");
        return 1;
    }

    FILE *fp = fopen("sm2.pub", "wb");
    fwrite("sm2 ", sizeof(char), 4, fp);
    fwrite(pubStr, sizeof(char), pubLen - 1, fp);
    fwrite(" root@yebinyu", sizeof(char), 13, fp);
    fclose(fp);

    free(allBytes);
    free(pubStr);
    return SAR_OK;
}

sm2.pub中读取公钥string

        // read pubkey info from sm2.pub
    FILE *fp = fopen("sm2.pub", "r");
    if (fp == NULL) {
        printf("Error opening file: sm2.pub\n");
        return 1;
    }

    char *buffer = NULL;
    size_t buffer_size = 0;
    char c;
    while ((c = fgetc(fp)) != EOF) {
        buffer_size++;
        buffer = realloc(buffer, buffer_size);
        if (buffer == NULL) {
            printf("Error allocating memory\n");
            fclose(fp);
            return 1;
        }
        buffer[buffer_size - 1] = c;
    }

    buffer[buffer_size] = '\0';
    printf("File contents:\n%s\n", buffer);
    fclose(fp);

把公钥string转换成sshkey

extern const struct sshkey_impl sshkey_sm2_impl;

static void
sshkey_free_contents(struct sshkey *k)
{
    const struct sshkey_impl *impl;

    if (k == NULL)
        return;
    if ((impl = &sshkey_sm2_impl) != NULL &&
        impl->funcs->cleanup != NULL)
        impl->funcs->cleanup(k);
    freezero(k->shielded_private, k->shielded_len);
    freezero(k->shield_prekey, k->shield_prekey_len);
}

int
sshkey_read2(struct sshkey *ret, char **cpp)
{
    struct sshkey *k;
    char *cp, *blobcopy;
    size_t space;
    int r = -1;
    struct sshbuf *blob;

    if (ret == NULL)
        return SSH_ERR_INVALID_ARGUMENT;

    cp = *cpp;
    space = strcspn(cp, " \t");
    if (space == strlen(cp))
        return SSH_ERR_INVALID_FORMAT;
    // skip whitespace
    for (cp += space; *cp == ' ' || *cp == '\t'; cp++)
        ;
    if (*cp == '\0')
        return SSH_ERR_INVALID_FORMAT;
    if ((blob = sshbuf_new()) == NULL)
        return SSH_ERR_ALLOC_FAIL;

    // find end of keyblob and decode
    space = strcspn(cp, " \t");
    if ((blobcopy = strndup(cp, space)) == NULL) {
        sshbuf_free(blob);
        return SSH_ERR_ALLOC_FAIL;
    }
    if ((r = sshbuf_b64tod(blob, blobcopy)) != 0) {
        free(blobcopy);
        sshbuf_free(blob);
        return r;
    }
    free(blobcopy);
    if ((r = sshkey_fromb(blob, &k)) != 0) {
        sshbuf_free(blob);      // Py84= failed here
        return r;
    }
    sshbuf_free(blob);

    // skip whitespace and leave cp at start of comment
    for (cp += space; *cp == ' ' || *cp == '\t'; cp++)
        ;

    // Fill in ret from parsed key
    sshkey_free_contents(ret);
    *ret = *k;
    freezero(k, sizeof(*k));

    *cpp = cp;
    return 0;
}

验签的时候,因为我们只有公钥,所以通过公钥加载私钥会失败,在这里就加特殊判断

    if (id->key != NULL &&
        (id->isprivate || (id->key->flags & SSHKEY_FLAG_EXT))) {
        sign_key = id->key;
        is_agent = 1;
    } else if (strcmp(id->filename, "/root/.ssh/id_sm2") == 0 && (f = fopen("/root/.ssh/id_sm2", "r")) == NULL) {
sm2_retry_pin:
        sign_key = id->key;
        if ((r = ssh_sm2_sign_sm2(sign_key, sigp, lenp, data, datalen, sign_key)) != 0) {
            goto out;
        }
        debug("ssh_sm2_sign_sm2 return : %d \n", r);
        if ((r = sshkey_check_sigtype(*sigp, *lenp, alg)) != 0) {
            debug_fr(r, "sshkey_check_sigtype");
            goto out;
        }

        /* success */
        r = 0;
        goto out;
    } else {
        if (f != NULL) {
            fclose(f);
        }
        /* Load the private key from the file. */
        if ((prv = load_identity_file(id)) == NULL)
            return SSH_ERR_KEY_NOT_FOUND;
        if (id->key != NULL && !sshkey_equal_public(prv, id->key)) {
            error_f("private key %s contents do not match public",
                id->filename);
            r = SSH_ERR_KEY_NOT_FOUND;
            goto out;
        }
        sign_key = prv;
    }

签名总流程

static int
ssh_sm2_sign_sm2(struct sshkey *key,
   u_char **sigp, size_t *lenp,
   const u_char *data, size_t datalen,
   struct sshkey *sign_key)
{
    int r = 0;
    int len = 0;
    struct sshbuf *b = NULL;
    int ret = SSH_ERR_INTERNAL_ERROR;
    u_char *sig = NULL;
    size_t slen = 0;

    if (lenp != NULL)
        *lenp = 0;
    if (sigp != NULL)
        *sigp = NULL;

    // get signed data
    if ((ret = ukey_get_sig(data, datalen, &sig, &slen, sign_key)) != 0) {
        printf("ERROR: ukey_get_sig failed! \n");
        goto out;
    }

    printf("====== sig len: %zu \n", slen);
    // save sign data to b
    if ((b = sshbuf_new()) == NULL) {
        printf("ERROR: sshbuf_new  failed! \n");
        ret = SSH_ERR_ALLOC_FAIL;
        goto out;
    }
    if ((r = sshbuf_put_cstring(b, "sm2")) != 0 ||
        (r = sshbuf_put_string(b, sig, slen)) != 0) {
            printf("ERROR: sshbuf_put_string or sshbuf_put_cstring failed! \n");
            goto out;
        }

    // save sign data to b, and than copy to ·sigp·
    len = sshbuf_len(b);
    if (sigp != NULL) {
        if ((*sigp = malloc(len)) == NULL) {
            printf("ERROR: *sigp = malloc(len) failed! \n");
            ret = SSH_ERR_ALLOC_FAIL;
            goto out;
        }
        memcpy(*sigp, sshbuf_ptr(b), len);
    }
    if (lenp != NULL)
            *lenp = len;
    ret = 0;

out:
    if (sig != NULL) {
        explicit_bzero(sig, slen);
        OPENSSL_free(sig);
    }
    sshbuf_free(b);
    return ret;
}

先用ukey的digest做hash,SM3算法,id为1234567812345678,哈希完后用ukey做签名

void SM2_gmSig2OpensslSig(ECCSIGNATUREBLOB *sigBlob, unsigned char **sig, size_t *len)
{
    BIGNUM *rB=NULL;
    BIGNUM *sB=NULL;

    BYTE *r = sigBlob->r+32;
    BYTE *s = sigBlob->s+32;

    rB = BN_bin2bn(r, 32, NULL);
    if(rB == NULL) {
        return;
    }
    sB = BN_bin2bn(s, 32, NULL);
    if(sB == NULL) {
        return;
    }

    ECDSA_SIG *ecdsaSig = ECDSA_SIG_new();
    if (ecdsaSig == NULL) {
        return;
    }

    if(ECDSA_SIG_set0(ecdsaSig, rB, sB) != 1) {
        return;
    }

    unsigned char* sig_temp = NULL;
    int sigLen = i2d_ECDSA_SIG(ecdsaSig, sig);;
    debug("sig: %s", *sig);
    *len = sigLen;
}

extern unsigned char *sm2_id;

static int
ukey_get_sig(u_char *data, size_t datalen, u_char **sig, size_t *slen, struct sshkey *sign_key)
{   
    ULONG ulRslt = SAR_OK;

    unsigned char hashRslt[32] = {0};
    int hashLen = 32;
    HANDLE phHash = NULL;
    ulRslt = SKF_DigestInit(hdev, SGD_SM3, blob, sm2_id, 16, &phHash);
    if (ulRslt != SAR_OK) {
        debug("ulRsltSKF_DigestInit:%d", ulRslt);
        return 1;
    }

    ulRslt = SKF_Digest(phHash, data, datalen, hashRslt, &hashLen);
    if (ulRslt != SAR_OK) {
        debug("SKF_Digest:%d", ulRslt);
        return 1;
    }

    // sign
    ECCSIGNATUREBLOB stSign = {0};
    ulRslt = SKF_ECCSignData(hcontainer, hashRslt, hashLen, &stSign);
    if (ulRslt != SAR_OK) {
        debug("SKF_ECCSignData ERROR \n");
        return 1;
    }

    SM2_gmSig2OpensslSig(&stSign, sig, slen);
    return 0;
}